migration

This commit is contained in:
Daniel Arantes Loverde
2026-08-11 13:22:02 -03:00
parent c4d6998595
commit 7702836fe7
231 changed files with 4329 additions and 1958 deletions

View File

@@ -0,0 +1,196 @@
import Foundation
import Testing
@testable import PediFoods
@Test("makeUserKey prefers a non-empty profileId over email")
func makeUserKeyPrefersProfileId() {
let key = SessionStateStore.makeUserKey(profileId: "abc123", email: "user@example.com")
#expect(key == "id:abc123")
}
@Test("makeUserKey falls back to email when profileId is nil or blank")
func makeUserKeyFallsBackToEmail() {
#expect(SessionStateStore.makeUserKey(profileId: nil, email: "User@Example.com") == "email:user@example.com")
#expect(SessionStateStore.makeUserKey(profileId: " ", email: "user@example.com") == "email:user@example.com")
}
@Test("makeUserKey returns nil when both profileId and email are absent")
func makeUserKeyNilWhenBothAbsent() {
#expect(SessionStateStore.makeUserKey(profileId: nil, email: nil) == nil)
#expect(SessionStateStore.makeUserKey(profileId: " ", email: " ") == nil)
}
/// `SessionStateStore.defaults` is one shared static var - Swift Testing
/// runs `@Test`s concurrently by default, so any test that swaps it would
/// race every other test in this file. `.serialized` forces this suite's
/// tests to run one at a time instead.
@Suite(.serialized)
struct SessionStateStorePersistenceTests {
/// Points `SessionStateStore` at a throwaway, uniquely-named
/// `UserDefaults` suite for the duration of one test, and restores
/// `.standard` after. Internal (not private) so the cross-file
/// extension in CartStateTests.swift can reuse it both need to be
/// in this same serialization domain, see that file's comment.
func withIsolatedDefaults(_ body: () throws -> Void) rethrows {
let suiteName = "SessionStateStoreTests.\(UUID().uuidString)"
let suite = UserDefaults(suiteName: suiteName)!
let previous = SessionStateStore.defaults
SessionStateStore.defaults = suite
defer {
SessionStateStore.defaults = previous
suite.removePersistentDomain(forName: suiteName)
}
try body()
}
@Test("shouldPromptPushOptIn is true before the first prompt is ever recorded")
func shouldPromptPushOptInTrueInitially() {
withIsolatedDefaults {
#expect(SessionStateStore.shouldPromptPushOptIn())
}
}
@Test("shouldPromptPushOptIn is false immediately after recording a prompt")
func shouldPromptPushOptInFalseRightAfterRecording() {
withIsolatedDefaults {
SessionStateStore.recordPushOptInPrompted()
#expect(SessionStateStore.shouldPromptPushOptIn() == false)
}
}
@Test("saveAddress/loadAddress round-trips every field")
func addressRoundTrip() {
withIsolatedDefaults {
let state = AddressState(selectedId: "addr-1", display: "Rua A, 100", latitude: -23.5, longitude: -46.6)
SessionStateStore.saveAddress(state)
let loaded = SessionStateStore.loadAddress()
#expect(loaded?.selectedId == "addr-1")
#expect(loaded?.display == "Rua A, 100")
#expect(loaded?.latitude == -23.5)
}
}
@Test("loadAddress substitutes a placeholder display string for an empty one")
func addressLoadReplacesEmptyDisplay() {
withIsolatedDefaults {
SessionStateStore.saveAddress(AddressState(selectedId: nil, display: ""))
#expect(SessionStateStore.loadAddress()?.display == "Defina seu endereco")
}
}
@Test("clearAddress removes a previously saved address")
func addressClear() {
withIsolatedDefaults {
SessionStateStore.saveAddress(AddressState(selectedId: "addr-1", display: "Rua A"))
SessionStateStore.clearAddress()
#expect(SessionStateStore.loadAddress() == nil)
}
}
@Test("saveCart/loadCart round-trips items, addons, and total")
func cartRoundTrip() {
withIsolatedDefaults {
let item = CartItemState(
id: "item-1", productId: "p1", storeId: "s1", name: "Pizza",
addons: [CartItemAddonState(id: "a1", name: "Extra cheese", quantity: 1, unitPrice: 3.0)],
quantity: 2, unitPrice: 29.9
)
let cart = CartState(storeId: "s1", storeName: "Loja A", items: [item], total: 65.8)
SessionStateStore.saveCart(cart)
let loaded = SessionStateStore.loadCart()
#expect(loaded?.storeId == "s1")
#expect(loaded?.items.first?.name == "Pizza")
#expect(loaded?.items.first?.addons.first?.name == "Extra cheese")
#expect(loaded?.total == 65.8)
}
}
@Test("clearCart removes a previously saved cart")
func cartClear() {
withIsolatedDefaults {
SessionStateStore.saveCart(CartState(storeId: "s1", storeName: "Loja A", items: [], total: 0))
SessionStateStore.clearCart()
#expect(SessionStateStore.loadCart() == nil)
}
}
@Test("saveTrackedOrder inserts new orders at the front and updates existing ones in place")
func trackedOrdersInsertAndUpdate() {
withIsolatedDefaults {
SessionStateStore.saveTrackedOrder(PublicOrderResult(id: "o1", status: "PENDING"))
SessionStateStore.saveTrackedOrder(PublicOrderResult(id: "o2", status: "PENDING"))
var orders = SessionStateStore.loadTrackedOrders()
#expect(orders.map(\.id) == ["o2", "o1"])
SessionStateStore.saveTrackedOrder(PublicOrderResult(id: "o1", status: "COMPLETED"))
orders = SessionStateStore.loadTrackedOrders()
#expect(orders.map(\.id) == ["o2", "o1"])
#expect(orders.first(where: { $0.id == "o1" })?.status == "COMPLETED")
}
}
@Test("loadTrackedOrder finds a specific order by id")
func trackedOrderLookupById() {
withIsolatedDefaults {
SessionStateStore.saveTrackedOrder(PublicOrderResult(id: "o1", status: "PENDING"))
#expect(SessionStateStore.loadTrackedOrder(orderId: "o1")?.status == "PENDING")
#expect(SessionStateStore.loadTrackedOrder(orderId: "missing") == nil)
}
}
@Test("savePendingCartOrderId clears the value when given an empty string")
func pendingCartOrderIdClearsOnEmptyString() {
withIsolatedDefaults {
SessionStateStore.savePendingCartOrderId("order-1")
#expect(SessionStateStore.loadPendingCartOrderId() == "order-1")
SessionStateStore.savePendingCartOrderId(" ")
#expect(SessionStateStore.loadPendingCartOrderId() == nil)
}
}
@Test("saveOrderReview de-duplicates by orderId case- and whitespace-insensitively")
func orderReviewDedupesByOrderId() {
withIsolatedDefaults {
let review1 = OrderReviewRecord(
orderId: "Order-1", storeId: nil, shortId: nil, storeName: nil, storeLogoURL: nil,
createdAt: nil, submittedAt: "2026-01-01", rating: 4, comment: "Good",
orderPositiveTags: nil, orderImprovementTags: nil, deliverySentiment: nil,
deliveryPositiveTags: nil, deliveryNegativeTags: nil, appNps: nil, platform: nil,
editableUntil: nil, storeReplyUntil: nil, reviewWindowExpiresAt: nil,
storeReplyMessage: nil, storeReplyAt: nil
)
SessionStateStore.saveOrderReview(review1)
#expect(SessionStateStore.hasOrderReview(orderId: "order-1"))
#expect(SessionStateStore.loadOrderReviews().count == 1)
let review2 = OrderReviewRecord(
orderId: "order-1 ", storeId: nil, shortId: nil, storeName: nil, storeLogoURL: nil,
createdAt: nil, submittedAt: "2026-01-02", rating: 5, comment: "Even better",
orderPositiveTags: nil, orderImprovementTags: nil, deliverySentiment: nil,
deliveryPositiveTags: nil, deliveryNegativeTags: nil, appNps: nil, platform: nil,
editableUntil: nil, storeReplyUntil: nil, reviewWindowExpiresAt: nil,
storeReplyMessage: nil, storeReplyAt: nil
)
SessionStateStore.saveOrderReview(review2)
#expect(SessionStateStore.loadOrderReviews().count == 1)
#expect(SessionStateStore.loadOrderReviews().first?.rating == 5)
}
}
@Test("saveOrderReviewDraft/loadOrderReviewDraft round-trips and clearOrderReviewDraft removes it")
func orderReviewDraftRoundTripAndClear() {
withIsolatedDefaults {
let draft = OrderReviewDraftState(
orderId: "o1", orderRate: 5, orderComment: "Great", orderPositiveTags: ["fast"],
orderImprovementTags: [], deliverySentiment: "good", deliveryPositiveTags: [],
deliveryNegativeTags: [], appNps: 9, platform: "ios"
)
SessionStateStore.saveOrderReviewDraft(draft)
#expect(SessionStateStore.loadOrderReviewDraft(orderId: "o1")?.orderComment == "Great")
SessionStateStore.clearOrderReviewDraft(orderId: "o1")
#expect(SessionStateStore.loadOrderReviewDraft(orderId: "o1") == nil)
}
}
}