migration
This commit is contained in:
161
PediFoodsTests/ApiModelsDecodingTests.swift
Normal file
161
PediFoodsTests/ApiModelsDecodingTests.swift
Normal file
@@ -0,0 +1,161 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import PediFoods
|
||||
|
||||
private func decode<T: Decodable>(_ 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")
|
||||
}
|
||||
Reference in New Issue
Block a user