import Foundation import Testing @testable import TodoApp /// A store on a fresh database file, removed afterwards. func withStore(_ body: (Store, String) async throws -> T) async throws -> T { let dir = FileManager.default.temporaryDirectory.appendingPathComponent("todo-swift-test-\(UUID().uuidString)") try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true) defer { try? FileManager.default.removeItem(at: dir) } let path = dir.appendingPathComponent("app.db").path let store = try await Store.open(path: path) do { let result = try await body(store, path) try await store.close() return result } catch { try? await store.close() throw error } } @Suite struct StoreTests { @Test func usesWALAndRecordsTheSchemaVersion() async throws { try await withStore { store, path in #expect(try await store.db.query("PRAGMA journal_mode").first?.column("journal_mode")?.string == "wal") let version = try await store.db.query("PRAGMA user_version").first?.column("user_version")?.integer #expect(version == Store.migrations.count) // Opening again runs nothing twice and keeps the data. let list = try await store.createList(token: Visitors.newToken()) try await store.add(list: list, title: "kept") let again = try await Store.open(path: path) #expect(try await again.todos(list: list).map(\.title) == ["kept"]) try await again.close() } } @Test func toggleAllCompletesEverythingThenReopensEverything() async throws { try await withStore { store, _ in let list = try await store.createList(token: Visitors.newToken()) for title in ["a", "b", "c"] { try await store.add(list: list, title: title) } let first = try await store.todos(list: list)[0].id #expect(try await store.toggle(list: list, id: first)) try await store.toggleAll(list: list) #expect(try await store.todos(list: list).allSatisfy(\.completed)) try await store.toggleAll(list: list) #expect(try await store.todos(list: list).allSatisfy { !$0.completed }) } } @Test func listsAreSeparate() async throws { try await withStore { store, _ in let mine = try await store.createList(token: Visitors.newToken()) let theirs = try await store.createList(token: Visitors.newToken()) try await store.add(list: mine, title: "mine") let id = try await store.todos(list: mine)[0].id #expect(try await store.toggle(list: theirs, id: id) == false) #expect(try await store.rename(list: theirs, id: id, title: "x") == false) #expect(try await store.destroy(list: theirs, id: id) == false) try await store.toggleAll(list: theirs) try await store.clearCompleted(list: theirs) #expect(try await store.todos(list: mine) == [Todo(id: id, title: "mine", completed: false)]) #expect(try await store.todos(list: theirs).isEmpty) } } @Test func clearCompletedKeepsActiveItems() async throws { try await withStore { store, _ in let list = try await store.createList(token: Visitors.newToken()) for title in ["a", "b", "c"] { try await store.add(list: list, title: title) } let b = try await store.todos(list: list)[1].id _ = try await store.toggle(list: list, id: b) try await store.clearCompleted(list: list) #expect(try await store.todos(list: list).map(\.title) == ["a", "c"]) } } } @Suite struct OptionsTests { @Test func socketMode() throws { let o = try Options(arguments: ["--socket", "/run/a.sock", "--db", "/d/app.db"], environment: ["CARLOS_HOST": "todo-swift.bfj.oncarlos.com"]) #expect(o.address == .socket("/run/a.sock")) #expect(o.origin == "https://todo-swift.bfj.oncarlos.com") #expect(o.servedOverHTTPS) } @Test func listenMode() throws { let o = try Options(arguments: ["--listen", "127.0.0.1:8080", "--db", "x.db"], environment: [:]) #expect(o.address == .listen(host: "127.0.0.1", port: 8080)) #expect(o.origin == "http://127.0.0.1:8080") #expect(!o.servedOverHTTPS) } @Test(arguments: [ ["--listen", "0.0.0.0:8080", "--db", "x.db"], ["--listen", ":8080", "--db", "x.db"], ["--listen", "127.0.0.1:8080"], ["--socket", "a.sock", "--listen", "127.0.0.1:1", "--db", "x.db"], ["--db"], ]) func refusesBadArguments(_ arguments: [String]) { #expect(throws: Options.UsageError.self) { try Options(arguments: arguments, environment: [:]) } } } @Suite struct AssetTests { static let appDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent() @Test(arguments: [ ("../../shared/base.css", Assets.baseCSS), ("../../shared/index.css", Assets.indexCSS), ("static/app.css", Assets.appCSS), ("static/app.js", Assets.appJS), ]) func embeddedFilesMatchTheirSources(_ path: String, _ embedded: String) throws { let source = try Data(contentsOf: Self.appDir.appendingPathComponent(path)) #expect(Data(embedded.utf8) == source, "run tools/embed.sh") } }