import Testing @testable import Persistence @Suite( "AccountRepository repository", .tags(.repository) ) struct AccountRepositoryTests { // MARK: Methods tests @Test func `finds the seeded demo buyer`() async throws { try await withDatabase { fluent in let user = try await AccountRepository(fluent: fluent).account(email: AccountRepository.demoEmail) #expect(user.email == "mara.v@example.com") #expect(user.displayName == "Mara Visser") } } @Test func `joins an unknown email once, named after its local part`() async throws { try await withDatabase { fluent in let repository = AccountRepository(fluent: fluent) let joined = try await repository.account(email: "sam@example.com") let returning = try await repository.account(email: "sam@example.com") #expect(joined.displayName == "sam") #expect(returning == joined) } } } // MARK: - Passkeys extension AccountRepositoryTests { @Test func `joins an account together with its first passkey`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) let free = try await accounts.isUnregistered(credentialID: "cred-1") let sam = try #require(try await accounts.join(email: "sam@example.com", credentialID: "cred-1", publicKey: [1, 2, 3], signCount: 0)) let passkey = try await accounts.passkey(credentialID: "cred-1") let byEmail = try await accounts.passkeys(email: "sam@example.com") let taken = try await accounts.isUnregistered(credentialID: "cred-1") #expect(free) #expect(!taken) #expect(sam.displayName == "sam") #expect(passkey == Passkey(credentialID: "cred-1", publicKey: [1, 2, 3], signCount: 0, user: sam)) #expect(byEmail == [passkey]) } } @Test func `never adds a passkey to an account that already exists`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) let hijack = try await accounts.join(email: AccountRepository.demoEmail, credentialID: "cred-x", publicKey: [9], signCount: 0) let passkeys = try await accounts.passkeys(email: AccountRepository.demoEmail) let stillFree = try await accounts.isUnregistered(credentialID: "cred-x") #expect(hijack == nil) #expect(passkeys.isEmpty) #expect(stillFree) } } @Test func `records a passkey's signature counter, and finds accounts without creating them`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) _ = try await accounts.join(email: "sam@example.com", credentialID: "cred-1", publicKey: [1], signCount: 0) try await accounts.recordUse(ofPasskey: "cred-1", signCount: 7) let passkey = try await accounts.passkey(credentialID: "cred-1") let known = try await accounts.existingAccount(email: "sam@example.com") let unknown = try await accounts.existingAccount(email: "nobody@example.com") let stillUnknown = try await accounts.existingAccount(email: "nobody@example.com") #expect(passkey?.signCount == 7) #expect(known?.email == "sam@example.com") #expect(unknown == nil) #expect(stillUnknown == nil) } } } // MARK: - Accounts and shops extension AccountRepositoryTests { @Test func `says whether a sign-in created the account`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) let first = try await accounts.signIn(email: "sam@example.com") let again = try await accounts.signIn(email: "sam@example.com") #expect(first.isNew) #expect(!again.isNew) #expect(first.user == again.user) } } @Test func `renames an account, and manages the passkeys of its own`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) let mara = try await accounts.account(email: AccountRepository.demoEmail) let sam = try await accounts.account(email: "sam@example.com") let renamed = try await accounts.rename(mara, to: "Mara V.") let stored = try await accounts.existingAccount(email: AccountRepository.demoEmail) try await accounts.addPasskey(to: mara, credentialID: "cred-m", publicKey: [1], signCount: 0) try await accounts.revokePasskey("cred-m", of: sam) let kept = try await accounts.passkeys(of: mara) try await accounts.revokePasskey("cred-m", of: mara) let revoked = try await accounts.passkeys(of: mara) #expect(renamed.displayName == "Mara V.") #expect(stored?.displayName == "Mara V.") #expect(kept.map(\.passkey.credentialID) == ["cred-m"]) #expect(revoked.isEmpty) } } @Test func `opens one shop per account, under a handle of its own`() async throws { try await withDatabase { fluent in let accounts = AccountRepository(fluent: fluent) let catalogue = CatalogueRepository(fluent: fluent) let mara = try await accounts.account(email: AccountRepository.demoEmail) let sam = try await accounts.account(email: "sam@example.com") let draft = ShopDraft( name: "Mara's Longbox", handle: "maras-longbox", city: "Haarlem", country: "NL", about: "Spare copies.", kind: .privateSeller, kvkNumber: "12345678", vatID: nil, legalName: nil, returnsNote: "14-day returns", handlingDays: 2 ) let shop = try await catalogue.openShop(draft, for: mara) let owned = try await TradeRepository(fluent: fluent).shop(ownedBy: mara) #expect(shop.slug == "maras-longbox") #expect(shop.kind == .privateSeller) // A private seller shows no registration, whatever was typed. #expect(shop.kvkNumber == nil) #expect(!shop.isVerified) #expect(shop.ratedSales == 0) #expect(owned == shop) await #expect(throws: ShopError.alreadyOwner) { try await catalogue.openShop(draft, for: mara) } await #expect(throws: ShopError.handleTaken) { try await catalogue.openShop(draft, for: sam) } // And the new shop sells: its owner lists a copy, which a buyer finds. let listing = try await catalogue.publish(ListingDraftTests.draft(), by: mara) #expect(listing?.shop.slug == "maras-longbox") } } }