// // Copyright 2026 Novaloop AG // // 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 Foundation import Testing final class HealthChatAccountStatusLoaderTests { private let apiService = HealthChatAPIServiceMock() @Test func reportsPendingApprovalForAnUnapprovedAccount() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: false)) #expect(await status() == .pendingApproval) #expect(apiService.profileCompletenessCallsCount == 0) } @Test func reportsTheIncompleteProfileMessage() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: true)) apiService.profileCompletenessReturnValue = try .success(makeCompleteness(isComplete: false, message: "Profil unvollständig")) #expect(await status() == .incompleteProfile(message: "Profil unvollständig")) } @Test func reportsOKForAnApprovedAccountWithACompleteProfile() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: true)) apiService.profileCompletenessReturnValue = try .success(makeCompleteness(isComplete: true, message: nil)) #expect(await status() == .ok) } @Test func reportsOKWhenTheServerWordsNoHint() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: true)) apiService.profileCompletenessReturnValue = try .success(makeCompleteness(isComplete: false, message: nil)) #expect(await status() == .ok) } @Test func staysUnknownWhenTheProfileCannotBeRead() async { apiService.ownProfileReturnValue = .failure(.networkFailure) #expect(await status() == .unknown) } @Test func staysUnknownWhenCompletenessCannotBeRead() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: true)) apiService.profileCompletenessReturnValue = .failure(.networkFailure) #expect(await status() == .unknown) } @Test func fetchesNothingUntilAsked() throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: false)) let loader = HealthChatAccountStatusLoader(apiService: apiService) #expect(loader.status == .unknown) #expect(apiService.ownProfileCallsCount == 0) } @Test func loadingAgainReflectsTheNewServerState() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: true)) apiService.profileCompletenessReturnValue = try .success(makeCompleteness(isComplete: false, message: "Profil unvollständig")) let loader = HealthChatAccountStatusLoader(apiService: apiService) loader.load() await loader.loadingTask?.value #expect(loader.status == .incompleteProfile(message: "Profil unvollständig")) apiService.profileCompletenessReturnValue = try .success(makeCompleteness(isComplete: true, message: nil)) loader.load() await loader.loadingTask?.value #expect(loader.status == .ok) } @Test func aFailedReloadKeepsTheLastKnownStatus() async throws { apiService.ownProfileReturnValue = try .success(makeMember(isApproved: false)) let loader = HealthChatAccountStatusLoader(apiService: apiService) loader.load() await loader.loadingTask?.value #expect(loader.status == .pendingApproval) apiService.ownProfileReturnValue = .failure(.networkFailure) loader.load() await loader.loadingTask?.value #expect(loader.status == .pendingApproval) } // MARK: - Helpers private func status() async -> HealthChatAccountStatus { let loader = HealthChatAccountStatusLoader(apiService: apiService) loader.load() await loader.loadingTask?.value return loader.status } private func makeMember(isApproved: Bool) throws -> HealthChatMember { try JSONDecoder().decode(HealthChatMember.self, from: Data(""" { "userId": "@alice:healthchat.ch", "isApproved": \(isApproved) } """.utf8)) } private func makeCompleteness(isComplete: Bool, message: String?) throws -> HealthChatProfileCompleteness { let messageJSON = message.map { "\"\($0)\"" } ?? "null" return try JSONDecoder().decode(HealthChatProfileCompleteness.self, from: Data(""" { "isComplete": \(isComplete), "message": \(messageJSON) } """.utf8)) } }