import Foundation import Testing @testable import PediFoods private func decode(_ type: T.Type, _ json: String) throws -> T { try JSONDecoder().decode(T.self, from: Data(json.utf8)) } @Test("StoreSummary falls back to placeholder id/name when missing") func storeSummaryDefaultsWhenFieldsMissing() throws { let store = try decode(StoreSummary.self, "{}") #expect(store.name == "Loja") #expect(store.id.isEmpty == false) #expect(store.rating == nil) } @Test("StoreSummary reads reviewsCount from any of its historical key names") func storeSummaryReviewsCountKeyFallback() throws { let viaReviews = try decode(StoreSummary.self, #"{"id":"1","name":"A","reviews":7}"#) #expect(viaReviews.reviewsCount == 7) let viaTotalReviews = try decode(StoreSummary.self, #"{"id":"1","name":"A","totalReviews":9}"#) #expect(viaTotalReviews.reviewsCount == 9) let viaReviewsCount = try decode(StoreSummary.self, #"{"id":"1","name":"A","reviewsCount":3}"#) #expect(viaReviewsCount.reviewsCount == 3) } @Test("StoreSummary coerces rating/deliveryFee/distance from string or int payloads") func storeSummaryFlexibleNumberCoercion() throws { let asString = try decode(StoreSummary.self, #"{"id":"1","name":"A","rating":"4,5","deliveryFee":"7,90","distance":"1.2"}"#) #expect(asString.rating == 4.5) #expect(asString.deliveryFee == 7.90) #expect(asString.distance == 1.2) let asInt = try decode(StoreSummary.self, #"{"id":"1","name":"A","rating":5,"deliveryFee":0}"#) #expect(asInt.rating == 5.0) #expect(asInt.deliveryFee == 0.0) } @Test("StoreAddressInfo accepts either zipCode or lowercase zipcode key") func storeAddressInfoZipCodeKeyFallback() throws { let viaZipCode = try decode(StoreAddressInfo.self, #"{"zipCode":"01000-000"}"#) #expect(viaZipCode.zipCode == "01000-000") let viaZipcode = try decode(StoreAddressInfo.self, #"{"zipcode":"02000-000"}"#) #expect(viaZipcode.zipCode == "02000-000") } @Test("StorePaymentMethodsInfo computed hasAny* flags are false when nothing accepted") func paymentMethodsHasAnyFlagsDefaultFalse() throws { let none = try decode(StorePaymentMethodsInfo.self, "{}") #expect(none.hasAnyCreditCard == false) #expect(none.hasAnyDebitCard == false) #expect(none.hasAnyVoucher == false) } @Test("StorePaymentMethodsInfo computed hasAny* flags are true if any one brand is accepted") func paymentMethodsHasAnyFlagsTrueOnSingleBrand() throws { let visaOnly = try decode(StorePaymentMethodsInfo.self, #"{"acceptCreditVisa":true}"#) #expect(visaOnly.hasAnyCreditCard == true) #expect(visaOnly.hasAnyDebitCard == false) let voucherOnly = try decode(StorePaymentMethodsInfo.self, #"{"acceptVoucherSodexo":true}"#) #expect(voucherOnly.hasAnyVoucher == true) } @Test("StoreCatalogProduct decodes pizzaPrices as Double, Int, or comma-decimal String") func storeCatalogProductPizzaPricesMultiType() throws { let asDouble = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","pizzaPrices":{"M":29.9}}"#) #expect(asDouble.pizzaPrices["M"] == 29.9) let asInt = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","pizzaPrices":{"M":30}}"#) #expect(asInt.pizzaPrices["M"] == 30.0) let asString = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","pizzaPrices":{"M":"29,90"}}"#) #expect(asString.pizzaPrices["M"] == 29.90) let malformedString = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","pizzaPrices":{"M":"not-a-number"}}"#) #expect(malformedString.pizzaPrices.isEmpty) } @Test("StoreCatalogProduct falls back through image/cover/photo and description/desc key names") func storeCatalogProductImageAndDescriptionFallback() throws { let viaCover = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","cover":"cover.png"}"#) #expect(viaCover.image == "cover.png") let viaPhoto = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","photo":"photo.png"}"#) #expect(viaPhoto.image == "photo.png") let viaDesc = try decode(StoreCatalogProduct.self, #"{"id":"1","name":"P","desc":"short desc"}"#) #expect(viaDesc.description == "short desc") } @Test("StoreCatalogProduct falls back from addonGroups to legacy addons key") func storeCatalogProductAddonGroupsFallback() throws { let json = #"{"id":"1","name":"P","addons":[{"id":"g1","name":"Extras","items":[{"id":"i1","name":"Cheese","price":2.5}]}]}"# let product = try decode(StoreCatalogProduct.self, json) #expect(product.addonGroups.count == 1) #expect(product.addonGroups[0].items[0].price == 2.5) } @Test("StoreAddonGroup and StoreAddonItem fall back to placeholder id/name when missing") func addonGroupAndItemDefaults() throws { let group = try decode(StoreAddonGroup.self, "{}") #expect(group.name == "Adicionais") #expect(group.items.isEmpty) let item = try decode(StoreAddonItem.self, "{}") #expect(item.name == "Item") #expect(item.price == nil) } @Test("CepLookupResult prefers direct keys over normalized over raw") func cepLookupResultPrefersDirectOverNormalizedOverRaw() throws { let json = """ { "zipCode": "01000-000", "normalized": { "cep": "02000-000", "logradouro": "Normalized St" }, "raw": { "cep": "03000-000", "address": "Raw St" } } """ let result = try decode(CepLookupResult.self, json) #expect(result.zipCode == "01000-000") #expect(result.street == "Normalized St") } @Test("CepLookupResult falls back to raw block when direct and normalized are absent") func cepLookupResultFallsBackToRawBlock() throws { let json = #"{"raw":{"cep":"03000-000","address":"Raw St","lat":"-23,55","lng":-46.6}}"# let result = try decode(CepLookupResult.self, json) #expect(result.zipCode == "03000-000") #expect(result.street == "Raw St") #expect(result.latitude == -23.55) #expect(result.longitude == -46.6) } @Test("CepLookupResult resolves via alternate Portuguese field names (bairro/cidade/uf)") func cepLookupResultPortugueseFieldNames() throws { let json = #"{"bairro":"Centro","cidade":"São Paulo","uf":"SP"}"# let result = try decode(CepLookupResult.self, json) #expect(result.neighborhood == "Centro") #expect(result.city == "São Paulo") #expect(result.state == "SP") } @Test("StoreCatalogCategory falls back to empty products array and placeholder id/name") func storeCatalogCategoryDefaults() throws { let category = try decode(StoreCatalogCategory.self, "{}") #expect(category.name == "Categoria") #expect(category.isPizzaCategory == false) #expect(category.products.isEmpty) } @Test("CustomerProfile decodes addressBook from the snake_case address_book key") func customerProfileAddressBookSnakeCaseKey() throws { let json = #"{"id":"1","name":"A","email":"a@a.com","address_book":[{"id":"addr1","city":"SP"}]}"# let profile = try decode(CustomerProfile.self, json) #expect(profile.addressBook?.count == 1) #expect(profile.addressBook?.first?.city == "SP") }