import Foundation import Testing @testable import gitbay private func makeClient() throws -> (GitbayClient, StubProtocol.Box) { let box = StubProtocol.box() let client = GitbayClient( instance: try GitbayInstance(url: "https://gitbay.org"), token: "test-token", session: box.session() ) return (client, box) } private func posted(_ stub: StubProtocol.Box) throws -> [[String]] { try stub.seen.filter { $0.method == "POST" }.map { seen in let body = try #require(try JSONSerialization.jsonObject(with: seen.body) as? [String: Any]) return try #require(body["argv"] as? [String]) } } private func scratchDefaults() -> UserDefaults { UserDefaults(suiteName: "gb-test-\(UUID().uuidString)")! } @MainActor struct AccountDeleteTests { @Test func aRequestIsRememberedForTheLinksDayAndNoLonger() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"user":"sim","mailed":"sim@example.test"},"exit_code":0}"#, match: "cmd")) let defaults = scratchDefaults() await AccountViewModel(client: client, defaults: defaults).requestDeletion(username: "sim") let later = AccountViewModel(client: client, defaults: defaults) later.loadPendingDeletion(username: "sim", now: .now.addingTimeInterval(60 * 60)) #expect(later.deletionMailedTo == "sim@example.test") let tooLate = AccountViewModel(client: client, defaults: defaults) tooLate.loadPendingDeletion(username: "sim", now: .now.addingTimeInterval(25 * 60 * 60)) #expect(tooLate.deletionMailedTo == nil) } @Test func withdrawingWhenNothingIsPendingClearsTheNotice() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 404, json: #"{"protocol_version":1,"error":"no deletion is pending for sim","exit_code":3}"#, match: "cmd")) let model = AccountViewModel(client: client, defaults: scratchDefaults()) await model.cancelDeletion(username: "sim") #expect(model.deletionMailedTo == nil) #expect(model.actionError == nil) } @Test func askingMailsALinkToTheAddressTheServerNames() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"user":"sim","mailed":"sim@example.test"},"exit_code":0}"#, match: "cmd")) let model = AccountViewModel(client: client, defaults: scratchDefaults()) await model.requestDeletion(username: "sim") #expect(try posted(stub) == [["account", "delete", "--confirm", "sim"]]) #expect(model.deletionMailedTo == "sim@example.test") } @Test func aRefusalSaysWhyAndNothingIsPending() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 403, json: #"{"protocol_version":1,"error":"you are the instance's only admin; promote another account first","exit_code":4}"#, match: "cmd")) let model = AccountViewModel(client: client, defaults: scratchDefaults()) await model.requestDeletion(username: "sim") #expect(model.deletionMailedTo == nil) #expect(model.actionError == "you are the instance's only admin; promote another account first") } @Test func withdrawingSendsCancel() async throws { let (client, stub) = try makeClient() stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"user":"sim","mailed":"sim@example.test"},"exit_code":0}"#, match: "cmd")) stub.enqueue(.init(status: 200, json: #"{"protocol_version":1,"data":{"user":"sim","cancelled":true},"exit_code":0}"#, match: "cmd")) let model = AccountViewModel(client: client, defaults: scratchDefaults()) await model.requestDeletion(username: "sim") await model.cancelDeletion(username: "sim") #expect(try posted(stub).last == ["account", "delete", "--cancel"]) #expect(model.deletionMailedTo == nil) } }