feat(gift): 선물 응답 모델을 추가한다
This commit is contained in:
@@ -0,0 +1,135 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct GiftFormOptionsResponse: Decodable {
|
||||||
|
let sizes: [GiftSizeOptionResponse]
|
||||||
|
let categories: [GiftCategoryOptionResponse]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftSizeOptionResponse: Decodable {
|
||||||
|
let sizeCode: String
|
||||||
|
let name: String
|
||||||
|
let basePriceCan: Int
|
||||||
|
let salePriceCan: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftCategoryOptionResponse: Decodable {
|
||||||
|
let categoryId: Int
|
||||||
|
let name: String
|
||||||
|
let requiresDamageWaiver: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CreateGiftRequest: Encodable {
|
||||||
|
let recipientMemberId: Int
|
||||||
|
let senderName: String
|
||||||
|
let senderPhoneNumber: String
|
||||||
|
let senderZipCode: String
|
||||||
|
let senderAddress: String
|
||||||
|
let senderAddressDetail: String
|
||||||
|
let sizeCode: String
|
||||||
|
let categoryId: Int
|
||||||
|
let senderTermsAgreed: Bool
|
||||||
|
let senderPrivacyAgreed: Bool
|
||||||
|
let damageWaiverAgreed: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CreateGiftResponse: Decodable {
|
||||||
|
let applicationNo: String
|
||||||
|
let status: String
|
||||||
|
let statusName: String
|
||||||
|
let priceCan: Int
|
||||||
|
let trackingDeadlineAt: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftListResponse: Decodable {
|
||||||
|
let totalCount: Int
|
||||||
|
let items: [GiftListItemResponse]
|
||||||
|
let page: Int
|
||||||
|
let size: Int
|
||||||
|
let hasNext: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftListItemResponse: Decodable {
|
||||||
|
let applicationNo: String
|
||||||
|
let direction: String
|
||||||
|
let status: String
|
||||||
|
let statusName: String
|
||||||
|
let priceCan: Int
|
||||||
|
let categoryName: String
|
||||||
|
let sizeName: String
|
||||||
|
let counterpartMemberId: Int
|
||||||
|
let counterpartNickname: String
|
||||||
|
let counterpartProfileImageUrl: String?
|
||||||
|
let createdAt: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftDetailResponse: Decodable {
|
||||||
|
let applicationNo: String
|
||||||
|
let direction: String
|
||||||
|
let status: String
|
||||||
|
let statusName: String
|
||||||
|
let giftInfo: GiftInfoResponse
|
||||||
|
let senderInfo: GiftSenderInfoResponse?
|
||||||
|
let recipientAddress: GiftRecipientAddressResponse?
|
||||||
|
let mailbox: GiftMailboxResponse?
|
||||||
|
let trackingRequired: Bool
|
||||||
|
let recipientAddressRequired: Bool
|
||||||
|
let recipientAddressDeadlineAt: String?
|
||||||
|
let delivery: GiftDeliveryResponse
|
||||||
|
let statusTimeline: [GiftStatusTimelineResponse]
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftInfoResponse: Decodable {
|
||||||
|
let recipientCreatorNickname: String?
|
||||||
|
let senderNickname: String?
|
||||||
|
let sizeName: String
|
||||||
|
let categoryName: String
|
||||||
|
let applicationNo: String?
|
||||||
|
let paidCan: Int?
|
||||||
|
let tracking: String?
|
||||||
|
let shippingRequestedAt: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftSenderInfoResponse: Decodable {
|
||||||
|
let name: String
|
||||||
|
let phoneNumber: String
|
||||||
|
let address: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftRecipientAddressResponse: Decodable {
|
||||||
|
let name: String
|
||||||
|
let phoneNumber: String
|
||||||
|
let address: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftMailboxResponse: Decodable {
|
||||||
|
let name: String
|
||||||
|
let address: String
|
||||||
|
let phoneNumber: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftDeliveryResponse: Decodable {
|
||||||
|
let canceledAt: String?
|
||||||
|
let undeliverableAt: String?
|
||||||
|
let undeliverableReason: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GiftStatusTimelineResponse: Decodable {
|
||||||
|
let status: String
|
||||||
|
let statusName: String
|
||||||
|
let occurredAt: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RegisterGiftTrackingRequest: Encodable {
|
||||||
|
let courierCompanyName: String
|
||||||
|
let trackingNumber: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SubmitGiftRecipientAddressRequest: Encodable {
|
||||||
|
let recipientName: String
|
||||||
|
let recipientPhoneNumber: String
|
||||||
|
let recipientZipCode: String
|
||||||
|
let recipientAddress: String
|
||||||
|
let recipientAddressDetail: String
|
||||||
|
let recipientTermsAgreed: Bool
|
||||||
|
let recipientPrivacyAgreed: Bool
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
@main
|
||||||
|
enum GiftDecodingContractCheck {
|
||||||
|
static var assertions = 0
|
||||||
|
|
||||||
|
static func check(_ condition: Bool, _ message: String) {
|
||||||
|
guard condition else { print("FAIL: \(message)"); exit(1) }
|
||||||
|
assertions += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
static func decode<T: Decodable>(_ type: T.Type, _ json: String) throws -> T {
|
||||||
|
try JSONDecoder().decode(type, from: Data(json.utf8))
|
||||||
|
}
|
||||||
|
|
||||||
|
static func main() {
|
||||||
|
let form = #"{"success":true,"message":"Options loaded","data":{"sizes":[{"sizeCode":"S","name":"Small","basePriceCan":100,"salePriceCan":80}],"categories":[{"categoryId":7,"name":"Flowers","requiresDamageWaiver":true}]}}"#
|
||||||
|
let create = #"{"success":true,"message":"Gift created","data":{"applicationNo":"G-1001","status":"CREATED","statusName":"Created","priceCan":80,"trackingDeadlineAt":"2026-10-10T12:00:00Z"}}"#
|
||||||
|
let list = #"{"success":true,"message":"Gifts loaded","data":{"totalCount":1,"items":[{"applicationNo":"G-1001","direction":"SENT","status":"CREATED","statusName":"Created","priceCan":80,"categoryName":"Flowers","sizeName":"Small","counterpartMemberId":42,"counterpartNickname":"Mina","counterpartProfileImageUrl":null,"createdAt":"2026-10-07T12:00:00Z"}],"page":1,"size":20,"hasNext":false}}"#
|
||||||
|
let detail = #"{"success":true,"message":"Gift detail loaded","data":{"applicationNo":"G-1001","direction":"SENT","status":"CREATED","statusName":"Created","giftInfo":{"recipientCreatorNickname":"Mina","senderNickname":"Jae","sizeName":"Small","categoryName":"Flowers","applicationNo":null,"paidCan":80,"tracking":"123456","shippingRequestedAt":null},"senderInfo":{"name":"Jae","phoneNumber":"010-1234-5678","address":"Seoul"},"recipientAddress":null,"mailbox":{"name":"Mina","address":"Busan","phoneNumber":"010-9876-5432"},"trackingRequired":true,"recipientAddressRequired":false,"recipientAddressDeadlineAt":null,"delivery":{"canceledAt":null,"undeliverableAt":null,"undeliverableReason":null},"statusTimeline":[{"status":"CREATED","statusName":"Created","occurredAt":"2026-10-07T12:00:00Z"}]}}"#
|
||||||
|
|
||||||
|
let options = try! decode(ApiResponse<GiftFormOptionsResponse>.self, form)
|
||||||
|
check(options.success, "form success")
|
||||||
|
check(options.data?.sizes.first?.sizeCode == "S" && options.data?.sizes.first?.name == "Small", "size fields")
|
||||||
|
check(options.data?.categories.first?.categoryId == 7 && options.data?.categories.first?.name == "Flowers", "category fields")
|
||||||
|
check(options.data?.categories.first?.requiresDamageWaiver == true, "waiver")
|
||||||
|
|
||||||
|
let created = try! decode(ApiResponse<CreateGiftResponse>.self, create)
|
||||||
|
check(created.data?.applicationNo == "G-1001" && created.data?.statusName == "Created", "create fields")
|
||||||
|
check(created.data?.priceCan == 80 && created.data?.trackingDeadlineAt == "2026-10-10T12:00:00Z", "create values")
|
||||||
|
|
||||||
|
let gifts = try! decode(ApiResponse<GiftListResponse>.self, list)
|
||||||
|
check(gifts.data?.totalCount == 1 && gifts.data?.page == 1 && gifts.data?.size == 20 && gifts.data?.hasNext == false, "list paging")
|
||||||
|
check(gifts.data?.items.first?.categoryName == "Flowers" && gifts.data?.items.first?.counterpartNickname == "Mina", "list values")
|
||||||
|
check(gifts.data?.items.first?.counterpartProfileImageUrl == nil, "list nullable profile image")
|
||||||
|
|
||||||
|
let gift = try! decode(ApiResponse<GiftDetailResponse>.self, detail).data!
|
||||||
|
check(gift.applicationNo == "G-1001" && gift.giftInfo.categoryName == "Flowers", "detail values")
|
||||||
|
check(gift.applicationNo == "G-1001" && gift.giftInfo.applicationNo == nil, "detail application numbers")
|
||||||
|
check(gift.senderInfo?.name == "Jae" && gift.mailbox?.name == "Mina", "detail nested values")
|
||||||
|
check(gift.recipientAddress == nil && gift.recipientAddressDeadlineAt == nil && gift.giftInfo.shippingRequestedAt == nil, "detail nullable values")
|
||||||
|
check(gift.statusTimeline.first?.statusName == "Created", "timeline status name")
|
||||||
|
|
||||||
|
for json in [
|
||||||
|
#"{"success":true,"message":"Gift canceled","data":{"applicationNo":"G-1001","status":"CANCELED","statusName":"Canceled","priceCan":80,"canceledAt":"2026-10-07T13:00:00Z"}}"#,
|
||||||
|
#"{"success":true,"message":"Tracking registered","data":{"applicationNo":"G-1001","status":"TRACKING_REGISTERED","statusName":"Tracking registered","courierCompanyName":"Fast Express","trackingNumber":"123456","trackingRegisteredAt":"2026-10-07T13:00:00Z","recipientAddressDeadlineAt":"2026-10-10T12:00:00Z"}}"#,
|
||||||
|
#"{"success":true,"message":"Recipient address registered","data":{"applicationNo":"G-1001","status":"ADDRESS_REGISTERED","statusName":"Address registered","recipientAddressRegisteredAt":"2026-10-07T14:00:00Z"}}"#
|
||||||
|
] {
|
||||||
|
check(try! decode(ApiResponseWithoutData.self, json).success, "mutation success envelope")
|
||||||
|
}
|
||||||
|
check(try! decode(ApiResponseWithoutData.self, #"{"success":true}"#).success, "envelope without data")
|
||||||
|
check(!(try! decode(ApiResponseWithoutData.self, #"{"success":false,"data":{"applicationNo":"G-1001"}}"#).success), "false success")
|
||||||
|
|
||||||
|
let size = try! decode(GiftSizeOptionResponse.self, #"{"sizeCode":"S","name":"Small","basePriceCan":100,"salePriceCan":80}"#)
|
||||||
|
check(size.name == "Small", "direct size.name")
|
||||||
|
let category = try! decode(GiftCategoryOptionResponse.self, #"{"categoryId":7,"name":"Flowers","requiresDamageWaiver":true}"#)
|
||||||
|
check(category.name == "Flowers", "direct category.name")
|
||||||
|
let timeline = try! decode(GiftStatusTimelineResponse.self, #"{"status":"CREATED","statusName":"Created","occurredAt":"2026-10-07T12:00:00Z"}"#)
|
||||||
|
check(timeline.statusName == "Created", "direct timeline.statusName")
|
||||||
|
print("PASS: \(assertions) assertions")
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||||
|
tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$tmp"' EXIT
|
||||||
|
|
||||||
|
swiftc \
|
||||||
|
"$root/SodaLive/Sources/Common/ApiResponse.swift" \
|
||||||
|
"$root/SodaLive/Sources/Common/ApiResponseWithoutData.swift" \
|
||||||
|
"$root/SodaLive/Sources/V2/Gift/Models/GiftModels.swift" \
|
||||||
|
"$root/work/tests/GiftDecodingContractCheck.swift" \
|
||||||
|
-o "$tmp/gift-decoding-contract-check"
|
||||||
|
|
||||||
|
"$tmp/gift-decoding-contract-check"
|
||||||
Reference in New Issue
Block a user