From d96a953b6f42a5745a05005a35c7dcf50b403cfd Mon Sep 17 00:00:00 2001 From: systemBlue Date: Mon, 1 Jun 2026 14:15:56 -0400 Subject: [PATCH] Don't trap when two accounts share a sourceKey Adding the same server and username twice yields two connections with the same sourceKey. PaginationState.rehydrate built its lookup with Dictionary(uniqueKeysWithValues:), which traps on a duplicate key, so every merged overview (notifications, issues/PRs, repositories) crashed on construction. Keep the first source instead, and add a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) --- Forji/Forji/Helpers/TaggedItem.swift | 5 ++++- Forji/ForjiTests/TaggedItemTests.swift | 29 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/Forji/Forji/Helpers/TaggedItem.swift b/Forji/Forji/Helpers/TaggedItem.swift index 64b8a3f..90b6b88 100644 --- a/Forji/Forji/Helpers/TaggedItem.swift +++ b/Forji/Forji/Helpers/TaggedItem.swift @@ -73,8 +73,11 @@ extension PaginationState where Item: Identifiable { /// Replace placeholder auth services using the given connection sources. /// Items whose sourceKey has no matching source are removed. func rehydrate(from sources: [ConnectionSource]) where Item == TaggedItem { + // Two connected accounts with the same server+username share a sourceKey, + // so keep the first rather than trapping on a duplicate key. let lookup = Dictionary( - uniqueKeysWithValues: sources.map { ($0.sourceKey, $0.authService) }, + sources.map { ($0.sourceKey, $0.authService) }, + uniquingKeysWith: { first, _ in first }, ) items = items.compactMap { item in guard let auth = lookup[item.sourceKey] else { return nil } diff --git a/Forji/ForjiTests/TaggedItemTests.swift b/Forji/ForjiTests/TaggedItemTests.swift index 34a24b2..d1171b1 100644 --- a/Forji/ForjiTests/TaggedItemTests.swift +++ b/Forji/ForjiTests/TaggedItemTests.swift @@ -190,4 +190,33 @@ struct TaggedItemTests { #expect(pagination.items.isEmpty) } + + @MainActor + @Test func rehydrateHandlesDuplicateSourceKeys() { + // Two connected accounts with the same server+username produce duplicate + // sourceKeys; rehydrate must not trap on the duplicate key. + let liveAuth = AuthenticationService() + let key = "https://a.com:u" + let sources = [ + ConnectionSource( + sourceKey: key, name: "A", + client: ForgejoClient(serverURL: "https://a.com", username: "u", token: "t"), + authService: liveAuth, + ), + ConnectionSource( + sourceKey: key, name: "A (duplicate)", + client: ForgejoClient(serverURL: "https://a.com", username: "u", token: "t"), + authService: AuthenticationService(), + ), + ] + let pagination = PaginationState>() + pagination.items = [ + TaggedItem(item: makeIssue(id: 1), sourceKey: key, instanceName: "A", authService: makeAuth()), + ] + + pagination.rehydrate(from: sources) + + #expect(pagination.items.count == 1) + #expect(pagination.items[0].authService === liveAuth) // first source wins + } }