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)) } private func makeOrder( status: String? = nil, paymentStatus: String? = nil, paymentConfirmed: Bool? = nil, customerOtp: String? = nil, otp: String? = nil, confirmOtp: String? = nil, timeline: [PublicOrderTimelineEvent] = [] ) -> PublicOrderResult { PublicOrderResult( id: "order-1", status: status, paymentStatus: paymentStatus, paymentConfirmed: paymentConfirmed, otp: otp, customerOtp: customerOtp, confirmOtp: confirmOtp, timeline: timeline ) } // MARK: - displayOtpCode @Test("PublicOrderResult.displayOtpCode prefers customerOtp, then otp, then confirmOtp") func displayOtpCodePriorityOrder() { let allThree = makeOrder(customerOtp: "1111", otp: "2222", confirmOtp: "3333") #expect(allThree.displayOtpCode == "1111") let otpAndConfirm = makeOrder(otp: "2222", confirmOtp: "3333") #expect(otpAndConfirm.displayOtpCode == "2222") let confirmOnly = makeOrder(confirmOtp: "3333") #expect(confirmOnly.displayOtpCode == "3333") } @Test("PublicOrderResult.displayOtpCode skips blank/whitespace-only values") func displayOtpCodeSkipsBlankValues() { let order = makeOrder(customerOtp: " ", otp: "2222") #expect(order.displayOtpCode == "2222") } @Test("PublicOrderResult.displayOtpCode is nil when no OTP field is set") func displayOtpCodeNilWhenAbsent() { #expect(makeOrder().displayOtpCode == nil) } // MARK: - isInDeliveryRoute / isFinalStatus @Test("PublicOrderResult.isInDeliveryRoute recognizes OUT_FOR_DELIVERY and Portuguese equivalents") func isInDeliveryRouteRecognizesKnownStatuses() { #expect(makeOrder(status: "OUT_FOR_DELIVERY").isInDeliveryRoute) #expect(makeOrder(status: "em_rota").isInDeliveryRoute) #expect(makeOrder(status: "ON_ROUTE").isInDeliveryRoute) #expect(makeOrder(status: "PREPARING").isInDeliveryRoute == false) } @Test("PublicOrderResult.isFinalStatus matches COMPLETED, CANCELED, and REFUNDED exactly") func isFinalStatusMatchesTerminalStatuses() { #expect(makeOrder(status: "COMPLETED").isFinalStatus) #expect(makeOrder(status: "CANCELED").isFinalStatus) #expect(makeOrder(status: "REFUNDED").isFinalStatus) #expect(makeOrder(status: "PREPARING").isFinalStatus == false) } // MARK: - isPaymentConfirmed @Test("PublicOrderResult.isPaymentConfirmed trusts the explicit paymentConfirmed flag first") func isPaymentConfirmedTrustsExplicitFlag() { #expect(makeOrder(paymentStatus: "PENDING", paymentConfirmed: true).isPaymentConfirmed) #expect(makeOrder(paymentStatus: "PAID", paymentConfirmed: false).isPaymentConfirmed == false) } @Test("PublicOrderResult.isPaymentConfirmed infers from a paymentStatus that looks confirmed") func isPaymentConfirmedInfersFromPaymentStatus() { #expect(makeOrder(paymentStatus: "CONFIRMED").isPaymentConfirmed) #expect(makeOrder(paymentStatus: "approved").isPaymentConfirmed) #expect(makeOrder(paymentStatus: "PENDING").isPaymentConfirmed == false) #expect(makeOrder(paymentStatus: "PAYMENT_FAILED").isPaymentConfirmed == false) } @Test("PublicOrderResult.isPaymentConfirmed falls back to the timeline when status fields don't confirm") func isPaymentConfirmedFallsBackToTimeline() { let confirmingEvent = PublicOrderTimelineEvent(status: "PAYMENT_APPROVED", message: nil, time: nil) let order = makeOrder(status: "PROCESSING", timeline: [confirmingEvent]) #expect(order.isPaymentConfirmed) } @Test("PublicOrderResult.isPaymentConfirmed is false with no confirming signal anywhere") func isPaymentConfirmedFalseWithNoSignal() { #expect(makeOrder(status: "PROCESSING").isPaymentConfirmed == false) } // MARK: - CreateOrderResult -> PublicOrderResult mapping @Test("CreateOrderResult.asPublicOrderResult maps every carried-over field") func createOrderResultMapsToPublicOrderResult() throws { let created = try decode(CreateOrderResult.self, #"{"id":"o1","shortId":"S1","status":"PENDING","paymentMethod":"PIX"}"#) let mapped = created.asPublicOrderResult() #expect(mapped.id == "o1") #expect(mapped.shortId == "S1") #expect(mapped.status == "PENDING") #expect(mapped.paymentMethod == "PIX") } // MARK: - Nested decode fallbacks @Test("PublicOrderDeliveryAddress falls back through street/address and zip/zipCode/zipcode key names") func publicOrderDeliveryAddressKeyFallback() throws { let viaAddress = try decode(PublicOrderDeliveryAddress.self, #"{"address":"Rua A","zipcode":"01000-000"}"#) #expect(viaAddress.street == "Rua A") #expect(viaAddress.zip == "01000-000") } @Test("PublicOrderItem falls back from qty to the legacy quantity key") func publicOrderItemQuantityKeyFallback() throws { let item = try decode(PublicOrderItem.self, #"{"quantity":3,"price":9.9}"#) #expect(item.qty == 3) } @Test("PublicOrderTimelineEvent falls back from message to event, and from time to createdAt/updatedAt") func publicOrderTimelineEventKeyFallback() throws { let viaEvent = try decode(PublicOrderTimelineEvent.self, #"{"status":"S","event":"Order placed"}"#) #expect(viaEvent.message == "Order placed") let viaCreatedAt = try decode(PublicOrderTimelineEvent.self, #"{"status":"S","createdAt":"2026-01-01T00:00:00Z"}"#) #expect(viaCreatedAt.time == "2026-01-01T00:00:00Z") } @Test("AppOrderSummary falls back through orderId/realId/id for its own id, and through many keys for storeLogoURL") func appOrderSummaryIdAndLogoFallback() throws { let viaRealId = try decode(AppOrderSummary.self, #"{"realId":"real-1"}"#) #expect(viaRealId.id == "real-1") let viaLogo = try decode(AppOrderSummary.self, #"{"orderId":"o1","store_logo":"logo.png"}"#) #expect(viaLogo.storeLogoURL == "logo.png") } @Test("AppOrderSummary falls back to the shared date key when createdAt/updatedAt are absent") func appOrderSummaryDateFallback() throws { let summary = try decode(AppOrderSummary.self, #"{"orderId":"o1","date":"2026-01-01"}"#) #expect(summary.createdAt == "2026-01-01") #expect(summary.updatedAt == "2026-01-01") }