import XCTest @testable import LChatCore // 🤖 The notification-tap resolution ladder (NotificationRoute): NSE origin // stamp → parked key → snapshot scan → only-space → last-active space. The // silent-drop bug this pins: a stale or missing SharedThreadKeys park used to // strand the tap on the chat list. final class NotificationRouteTests: XCTestCase { private func inst(_ origin: String, archived: Bool = false) -> SavedInstance { var i = SavedInstance(origin: origin, deviceToken: "t", screenName: "s", userPub: "pub", userPriv: "priv") i.archived = archived return i } func testStampedOriginWinsOverEverything() { // The NSE stamped the banner at delivery time — the route trusts it // before consulting any on-device state (which can churn between // delivery and tap). let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", origin: "https://b.example", instances: [a, b], entryOrigin: { _ in XCTFail("park consulted despite stamp"); return "https://a.example" }, snapshotHasThread: { _, _ in XCTFail("snapshot consulted despite stamp"); return false }) guard case .stamped(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, b.origin) } func testStaleStampFallsDownTheLadder() { // A stamp for a space that's since been signed out must not dead-end // the tap — the ladder still runs beneath it. let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", origin: "https://gone.example", instances: [a, b], entryOrigin: { _ in "https://a.example" }, snapshotHasThread: { _, _ in false }) guard case .parkedKey(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, a.origin) } func testParkedKeyWins() { let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", instances: [a, b], entryOrigin: { _ in "https://b.example" }, snapshotHasThread: { _, _ in XCTFail("snapshot consulted despite park"); return false }) guard case .parkedKey(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, b.origin) } func testStaleParkFallsBackToSnapshot() { // The park points at an origin that's no longer signed in (re-added // space) — the snapshot scan must still find the thread's real home. let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", instances: [a, b], entryOrigin: { _ in "https://gone.example" }, snapshotHasThread: { origin, thread in origin == "https://b.example" && thread == "th1" }) guard case .snapshot(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, b.origin) } func testMissingParkFallsBackToSnapshot() { let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", instances: [a, b], entryOrigin: { _ in nil }, snapshotHasThread: { origin, _ in origin == "https://a.example" }) guard case .snapshot(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, a.origin) } func testSingleSpaceAlwaysResolves() { // Nothing recorded anywhere (wiped keychain, brand-new thread): with // one signed-in space the tap still routes there. let a = inst("https://a.example") let r = NotificationRoute.resolve( threadID: "new-thread", instances: [a], entryOrigin: { _ in nil }, snapshotHasThread: { _, _ in false }) guard case .onlySpace(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, a.origin) } func testSingleActiveSpaceSkipsArchived() { let live = inst("https://live.example") let parked = inst("https://old.example", archived: true) let r = NotificationRoute.resolve( threadID: "th1", instances: [live, parked], entryOrigin: { _ in nil }, snapshotHasThread: { _, _ in false }) guard case .onlySpace(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, live.origin) } func testMultiSpaceNoEvidenceFallsBackToLastActive() { // Undecryptable push for a brand-new thread, several spaces: the // space this device was last in is the optimistic guess — the tap // opens a chat screen there instead of dropping on the list. let a = inst("https://a.example"), b = inst("https://b.example") let r = NotificationRoute.resolve( threadID: "th1", lastActiveID: "https://b.example", instances: [a, b], entryOrigin: { _ in nil }, snapshotHasThread: { _, _ in false }) guard case .lastActive(let hit) = r else { return XCTFail("\(r)") } XCTAssertEqual(hit.origin, b.origin) } func testMultiSpaceUnknownThreadStaysUnresolved() { // No last-active record either (or it names a space that's gone) — // only then does the tap drop. let a = inst("https://a.example"), b = inst("https://b.example") for last in [nil, "https://gone.example"] { let r = NotificationRoute.resolve( threadID: "th1", lastActiveID: last, instances: [a, b], entryOrigin: { _ in nil }, snapshotHasThread: { _, _ in false }) guard case .unresolved = r else { return XCTFail("\(r)") } } } }