Introduce a user session manager (#6295)

This commit is contained in:
Mauro
2026-10-09 14:07:39 +02:00
committed by GitHub
parent 65bab49f39
commit 60db364436
26 changed files with 594 additions and 66 deletions
@@ -0,0 +1,119 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
@testable import ElementX
import Testing
@MainActor
struct UserSessionManagerTests {
let userSessionStore = UserSessionStoreMock()
let appSettings = AppSettings.volatile()
@Test
func ordersAccountsByWhenTheyWereLastSelected() {
appSettings.userSettings(for: "@alice:matrix.org").account.lastSelectedDate = .now.addingTimeInterval(-60)
appSettings.userSettings(for: "@bob:matrix.org").account.lastSelectedDate = .now
// Accounts that were never selected (e.g. after upgrading from a single account) come last.
let manager = makeManager(userIDs: ["@dave:matrix.org", "@alice:matrix.org", "@carol:matrix.org", "@bob:matrix.org"])
#expect(manager.userIDs == ["@bob:matrix.org", "@alice:matrix.org", "@carol:matrix.org", "@dave:matrix.org"])
}
@Test
func restoreFallsBackToTheNextAccount() async {
let manager = makeManager(userIDs: ["@alice:matrix.org", "@bob:matrix.org"])
// Alice already has a live session, e.g. when restoring again after clearing the cache.
manager.add(makeUserSession(userID: "@alice:matrix.org"))
let bob = makeUserSession(userID: "@bob:matrix.org")
userSessionStore.restoreUserSessionUserIDClosure = { userID in
userID == "@bob:matrix.org" ? .success(bob) : .failure(.failedRestoringLogin)
}
guard case .success(let userSession) = await manager.restoreActiveSession() else {
Issue.record("Restoring should fall back to Bob.")
return
}
#expect(userSession.clientProxy.userID == "@bob:matrix.org")
#expect(manager.userIDs == ["@bob:matrix.org"])
#expect(manager.session(for: "@alice:matrix.org") == nil)
// Nothing can use the session until it's added, e.g. while migrations run.
#expect(manager.activeSession == nil)
}
@Test
func restoreFailsWhenNoAccountCanBeRestored() async {
let manager = makeManager(userIDs: ["@alice:matrix.org", "@bob:matrix.org"])
userSessionStore.restoreUserSessionUserIDReturnValue = .failure(.failedRestoringLogin)
guard case .failure(.failedRestoringSessions) = await manager.restoreActiveSession() else {
Issue.record("Restoring should fail once every account has failed.")
return
}
#expect(userSessionStore.restoreUserSessionUserIDReceivedInvocations == ["@alice:matrix.org", "@bob:matrix.org"])
#expect(manager.userIDs.isEmpty)
}
@Test
func addRegistersTheSession() {
let manager = makeManager(userIDs: ["@alice:matrix.org", "@bob:matrix.org"])
// A known account keeps its place, e.g. after clearing the cache.
let bob = makeUserSession(userID: "@bob:matrix.org")
manager.add(bob)
#expect(manager.userIDs == ["@alice:matrix.org", "@bob:matrix.org"])
#expect((manager.session(for: "@bob:matrix.org") as? UserSessionMock) === bob)
#expect(manager.activeSession == nil)
// A new account becomes the active one.
let carol = makeUserSession(userID: "@carol:matrix.org")
manager.add(carol)
#expect(manager.userIDs == ["@carol:matrix.org", "@alice:matrix.org", "@bob:matrix.org"])
#expect(appSettings.userSettings(for: "@carol:matrix.org").account.lastSelectedDate != nil)
#expect((manager.activeSession as? UserSessionMock) === carol)
#expect(manager.sessionsPublisher.value.map(\.clientProxy.userID) == ["@carol:matrix.org", "@bob:matrix.org"])
}
@Test
func addReplacesTheSessionOfAKnownAccount() {
let manager = makeManager(userIDs: ["@alice:matrix.org", "@bob:matrix.org"])
manager.add(makeUserSession(userID: "@alice:matrix.org"))
// E.g. restoring again after clearing the cache, or signing in again after a soft logout.
let newAlice = makeUserSession(userID: "@alice:matrix.org")
manager.add(newAlice)
#expect(manager.userIDs == ["@alice:matrix.org", "@bob:matrix.org"])
#expect((manager.activeSession as? UserSessionMock) === newAlice)
}
@Test
func removeDeletesTheAccount() {
let manager = makeManager(userIDs: ["@alice:matrix.org"])
let alice = makeUserSession(userID: "@alice:matrix.org")
manager.add(alice)
manager.remove(userID: "@alice:matrix.org")
#expect((userSessionStore.logoutUserSessionReceivedUserSession as? UserSessionMock) === alice)
#expect(manager.userIDs.isEmpty)
#expect(manager.activeSession == nil)
}
// MARK: - Helpers
private func makeManager(userIDs: [String]) -> UserSessionManager {
userSessionStore.userIDs = userIDs
return UserSessionManager(userSessionStore: userSessionStore, appSettings: appSettings)
}
private func makeUserSession(userID: String) -> UserSessionMock {
UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: userID))))
}
}