From b526dae7e1fdfad54d8dedadb71d3a5d7b10adfb Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Thu, 8 Oct 2026 12:33:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(gift):=20=EC=84=A0=EB=AC=BC=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EB=94=A5=EB=A7=81=ED=81=AC=EB=A5=BC=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SodaLive/Sources/App/AppDeepLinkHandler.swift | 11 +++ work/tests/AppDeepLinkGiftDetailCheck.swift | 88 +++++++++++++++++++ work/tests/check-app-deep-link-gift-detail.sh | 11 +++ 3 files changed, 110 insertions(+) create mode 100644 work/tests/AppDeepLinkGiftDetailCheck.swift create mode 100755 work/tests/check-app-deep-link-gift-detail.sh diff --git a/SodaLive/Sources/App/AppDeepLinkHandler.swift b/SodaLive/Sources/App/AppDeepLinkHandler.swift index 24b6171e..c16644db 100644 --- a/SodaLive/Sources/App/AppDeepLinkHandler.swift +++ b/SodaLive/Sources/App/AppDeepLinkHandler.swift @@ -5,6 +5,7 @@ enum AppDeepLinkAction { case userCreatorChat(roomId: Int) case content(contentId: Int) case series(seriesId: Int) + case giftDetail(applicationNo: String) case community(creatorId: Int, postId: Int?) case message case audition @@ -70,6 +71,9 @@ enum AppDeepLinkHandler { guard seriesId > 0 else { return } AppState.shared.setAppStep(step: .seriesDetail(seriesId: seriesId)) + case .giftDetail(let applicationNo): + AppState.shared.setAppStep(step: .giftDetail(applicationNo: applicationNo)) + case .community(let creatorId, let postId): guard creatorId > 0 else { return } @@ -148,6 +152,13 @@ enum AppDeepLinkHandler { return .series(seriesId: value) } + if let value = queryMap["deeplinkvalue"]?.trimmingCharacters(in: .whitespacesAndNewlines), + value.caseInsensitiveCompare("GIFT_DETAIL") == .orderedSame, + let applicationNo = queryMap["deeplinkid"]?.trimmingCharacters(in: .whitespacesAndNewlines), + !applicationNo.isEmpty { + return .giftDetail(applicationNo: applicationNo) + } + if let communityId = queryMap["community_id"], let value = Int(communityId), value > 0 { return .community(creatorId: value, postId: communityPostId(queryMap: queryMap)) } diff --git a/work/tests/AppDeepLinkGiftDetailCheck.swift b/work/tests/AppDeepLinkGiftDetailCheck.swift new file mode 100644 index 00000000..2372e610 --- /dev/null +++ b/work/tests/AppDeepLinkGiftDetailCheck.swift @@ -0,0 +1,88 @@ +import Foundation + +enum AppStep: Equatable { + case splash + case main + case userCreatorChatRoom(roomId: Int) + case contentDetail(contentId: Int) + case seriesDetail(seriesId: Int) + case creatorCommunityAll(creatorId: Int) + case message + case audition + case giftDetail(applicationNo: String) +} + +enum UserDefaultsKey { case userId } +enum UserDefaults { + static func int(forKey: UserDefaultsKey) -> Int { 0 } +} + +final class AppState { + static let shared = AppState() + var rootStep: AppStep = .main + var pendingDeepLinkAction: AppDeepLinkAction? + var appliedStep: AppStep? + var isPushRoomFromDeepLink = false + var pushRoomId = 0 + func setAppStep(step: AppStep) { appliedStep = step } + func setPendingCommunityCommentDeepLink(creatorId: Int, postId: Int) {} + func clearPendingCommunityCommentDeepLink() {} +} + +let APPSCHEME = "soda" + +@main +enum GiftDetailDeepLinkCheck { + static var assertions = 0 + + static func check(_ condition: @autoclosure () -> Bool, _ message: String) { + assertions += 1 + guard condition() else { fatalError("FAIL: \(message)") } + } + + static func settle() { + RunLoop.current.run(until: Date().addingTimeInterval(0.05)) + } + + static func main() { + let valid = "voiceon://?DeepLinkValue=gift_detail&DEEPLINKID=GIFT-2026-%30%30%31" + check(AppDeepLinkHandler.handle(urlString: valid), "valid gift detail accepted") + settle() + check(AppState.shared.appliedStep == .giftDetail(applicationNo: "GIFT-2026-001"), "decoded identifier routes to gift detail") + + AppState.shared.appliedStep = nil + check(AppDeepLinkHandler.handle(urlString: "voiceon://?deepLinkValue=%20GIFT_DETAIL%20&deepLinkId=%20GIFT-2026-002%20"), "trimmed semantic values accepted") + settle() + check(AppState.shared.appliedStep == .giftDetail(applicationNo: "GIFT-2026-002"), "identifier whitespace trimmed") + + for url in [ + "voiceon://?deepLinkValue=GIFT_DETAIL", + "voiceon://?deepLinkId=GIFT-2026-003", + "voiceon://?deepLinkValue=GIFT_DETAIL&deepLinkId=%20%20", + "voiceon://?deepLinkValue=OTHER&deepLinkId=GIFT-2026-004", + "http://?deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-005" + ] { + check(!AppDeepLinkHandler.handle(urlString: url), "rejects invalid or unsupported \(url)") + } + + AppState.shared.rootStep = .splash + AppState.shared.pendingDeepLinkAction = nil + AppState.shared.appliedStep = nil + check(AppDeepLinkHandler.handle(urlString: "voiceon://?deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-006"), "valid splash link accepted") + settle() + if case .giftDetail(let applicationNo)? = AppState.shared.pendingDeepLinkAction { + check(applicationNo == "GIFT-2026-006", "splash stores gift action") + } else { + check(false, "splash stores gift action") + } + check(AppState.shared.appliedStep == nil, "splash does not apply gift action") + + AppState.shared.rootStep = .main + AppState.shared.appliedStep = nil + AppState.shared.pendingDeepLinkAction = nil + check(AppDeepLinkHandler.handle(urlString: "voiceon://?content_id=42&deepLinkValue=GIFT_DETAIL&deepLinkId=GIFT-2026-007"), "existing content route accepted") + settle() + check(AppState.shared.appliedStep == .contentDetail(contentId: 42), "existing content route still applies") + print("PASS AppDeepLinkGiftDetailCheck assertions=\(assertions)") + } +} diff --git a/work/tests/check-app-deep-link-gift-detail.sh b/work/tests/check-app-deep-link-gift-detail.sh new file mode 100755 index 00000000..5cfd403e --- /dev/null +++ b/work/tests/check-app-deep-link-gift-detail.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -euo pipefail + +root="$(cd "$(dirname "$0")/../.." && pwd)" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +swiftc "$root/SodaLive/Sources/App/AppDeepLinkHandler.swift" \ + "$root/work/tests/AppDeepLinkGiftDetailCheck.swift" \ + -o "$tmp/check" +"$tmp/check"