native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
15 kB · 418 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419import Dependenciesimport DependenciesTestSupportimport Foundationimport Sharingimport Testing
@testable import supacode
struct RepositorySettingsKeyTests { @Test func encodingOmitsNilWorktreeBaseRef() throws { let data = try JSONEncoder().encode(RepositorySettings.default) let json = String(bytes: data, encoding: .utf8) ?? ""
#expect(!json.contains("worktreeBaseRef")) #expect(!json.contains("worktreeBaseDirectoryPath")) }
@Test(.dependencies) func loadCreatesDefaultAndPersists() throws { let storage = SettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo")
let settings = withDependencies { $0.settingsFileStorage = storage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(settings == RepositorySettings.default)
let saved: SettingsFile = withDependencies { $0.settingsFileStorage = storage.storage } operation: { @Shared(.settingsFile) var settings: SettingsFile return settings }
#expect( saved.repositories[rootURL.path(percentEncoded: false)] == RepositorySettings.default ) }
@Test(.dependencies) func saveOverwritesExistingSettings() throws { let storage = SettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo")
var updated = RepositorySettings.default updated.runScript = "echo updated"
withDependencies { $0.settingsFileStorage = storage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings $repositorySettings.withLock { $0 = updated } }
let reloaded: SettingsFile = withDependencies { $0.settingsFileStorage = storage.storage } operation: { @Shared(.settingsFile) var settings: SettingsFile return settings }
#expect(reloaded.repositories[rootURL.path(percentEncoded: false)] == updated) }
@Test func decodeMissingArchiveScriptDefaultsToEmpty() throws { let data = Data( """ { "setupScript": "echo setup", "runScript": "echo run", "openActionID": "automatic" } """.utf8 ) let settings = try JSONDecoder().decode(RepositorySettings.self, from: data)
#expect(settings.archiveScript.isEmpty) }
@Test(.dependencies) func loadNormalizesLegacyDefaultOverridesToInheritedValues() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let localURL = SupacodePaths.repositorySettingsURL(for: rootURL) let legacyData = Data( """ { "setupScript": "", "archiveScript": "", "runScript": "echo run", "openActionID": "automatic", "copyIgnoredOnWorktreeCreate": false, "copyUntrackedOnWorktreeCreate": true, "pullRequestMergeStrategy": "merge" } """.utf8 )
try localStorage.save(legacyData, at: localURL)
let loaded = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(loaded.copyIgnoredOnWorktreeCreate == nil) #expect(loaded.copyUntrackedOnWorktreeCreate == true) #expect(loaded.pullRequestMergeStrategy == nil) }
@Test func decodeCurrentSchemaPreservesExplicitDefaultOverrides() throws { let settings = RepositorySettings( setupScript: "", archiveScript: "", runScript: "echo run", openActionID: OpenWorktreeAction.automaticSettingsID, worktreeBaseRef: nil, copyIgnoredOnWorktreeCreate: false, copyUntrackedOnWorktreeCreate: false, pullRequestMergeStrategy: .merge, )
let decoded = try JSONDecoder().decode(RepositorySettings.self, from: encode(settings))
#expect(decoded.copyIgnoredOnWorktreeCreate == false) #expect(decoded.copyUntrackedOnWorktreeCreate == false) #expect(decoded.pullRequestMergeStrategy == .merge) }
@Test func decodeMissingObservationOverridesDefaultsToEnabled() throws { let data = Data( """ { "setupScript": "", "archiveScript": "", "runScript": "echo run", "openActionID": "automatic" } """.utf8 ) let settings = try JSONDecoder().decode(RepositorySettings.self, from: data)
#expect(settings.observeLineDiffsAutomatically == nil) #expect(settings.fetchPullRequestState == nil) #expect(settings.observesLineDiffsAutomatically) #expect(settings.fetchesPullRequestState) }
@Test func decodePreservesExplicitObservationOverrides() throws { var settings = RepositorySettings.default settings.observeLineDiffsAutomatically = false settings.fetchPullRequestState = false
let decoded = try JSONDecoder().decode(RepositorySettings.self, from: encode(settings))
#expect(decoded.observeLineDiffsAutomatically == false) #expect(decoded.fetchPullRequestState == false) #expect(!decoded.observesLineDiffsAutomatically) #expect(!decoded.fetchesPullRequestState) }
@Test(.dependencies) func loadPrefersLocalSupacodeJSONOverGlobalEntry() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let repositoryID = rootURL.standardizedFileURL.path(percentEncoded: false) var globalSettings = RepositorySettings.default globalSettings.runScript = "echo global" var localSettings = RepositorySettings.default localSettings.runScript = "echo local"
withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.settingsFile) var settingsFile: SettingsFile $settingsFile.withLock { $0.repositories[repositoryID] = globalSettings } }
try localStorage.save( encode(localSettings), at: SupacodePaths.repositorySettingsURL(for: rootURL) )
let loaded = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(loaded == localSettings) }
@Test(.dependencies) func loadFallsBackToGlobalWhenLocalMissing() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let repositoryID = rootURL.standardizedFileURL.path(percentEncoded: false) var globalSettings = RepositorySettings.default globalSettings.runScript = "echo global"
withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.settingsFile) var settingsFile: SettingsFile $settingsFile.withLock { $0.repositories[repositoryID] = globalSettings } }
let loaded = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(loaded == globalSettings) }
@Test(.dependencies) func loadFallsBackToGlobalWhenLocalInvalid() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let repositoryID = rootURL.standardizedFileURL.path(percentEncoded: false) let localURL = SupacodePaths.repositorySettingsURL(for: rootURL) var globalSettings = RepositorySettings.default globalSettings.runScript = "echo global"
withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.settingsFile) var settingsFile: SettingsFile $settingsFile.withLock { $0.repositories[repositoryID] = globalSettings } }
try localStorage.save(Data("{".utf8), at: localURL)
let loaded = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(loaded == globalSettings) }
@Test(.dependencies) func loadMigratesLegacyRepositoryRootFile() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let localURL = SupacodePaths.repositorySettingsURL(for: rootURL) let legacyURL = SupacodePaths.legacyRepositorySettingsURL(for: rootURL) var legacySettings = RepositorySettings.default legacySettings.runScript = "echo from-legacy"
try localStorage.save(encode(legacySettings), at: legacyURL)
let loaded = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings return repositorySettings }
#expect(loaded == legacySettings)
let localData = try #require(localStorage.data(at: localURL)) let localDecoded = try JSONDecoder().decode(RepositorySettings.self, from: localData) #expect(localDecoded == legacySettings) }
@Test(.dependencies) func saveWritesLocalWhenLocalFileExists() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let repositoryID = rootURL.standardizedFileURL.path(percentEncoded: false) let localURL = SupacodePaths.repositorySettingsURL(for: rootURL)
try localStorage.save(encode(.default), at: localURL)
var updated = RepositorySettings.default updated.runScript = "echo local"
withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings $repositorySettings.withLock { $0 = updated } }
let localData = try #require(localStorage.data(at: localURL)) let localDecoded = try JSONDecoder().decode(RepositorySettings.self, from: localData) #expect(localDecoded == updated)
let globalSaved: SettingsFile = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.settingsFile) var settingsFile: SettingsFile return settingsFile }
#expect(globalSaved.repositories[repositoryID] == nil) }
@Test(.dependencies) func saveWritesGlobalWhenLocalFileMissing() throws { let globalStorage = SettingsTestStorage() let localStorage = RepositoryLocalSettingsTestStorage() let rootURL = URL(fileURLWithPath: "/tmp/repo") let settingsFileURL = URL(fileURLWithPath: "/tmp/supacode-settings-\(UUID().uuidString).json") let repositoryID = rootURL.standardizedFileURL.path(percentEncoded: false) let localURL = SupacodePaths.repositorySettingsURL(for: rootURL)
var updated = RepositorySettings.default updated.runScript = "echo global"
withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.repositorySettings(rootURL)) var repositorySettings: RepositorySettings $repositorySettings.withLock { $0 = updated } }
let globalSaved: SettingsFile = withDependencies { $0.settingsFileStorage = globalStorage.storage $0.settingsFileURL = settingsFileURL $0.repositoryLocalSettingsStorage = localStorage.storage } operation: { @Shared(.settingsFile) var settingsFile: SettingsFile return settingsFile }
#expect(globalSaved.repositories[repositoryID] == updated) #expect(localStorage.data(at: localURL) == nil) }
private func encode(_ settings: RepositorySettings) throws -> Data { let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] return try encoder.encode(settings) }}
nonisolated final class RepositoryLocalSettingsTestStorage: @unchecked Sendable { private let lock = NSLock() private var dataByURL: [URL: Data] = [:]
var storage: RepositoryLocalSettingsStorage { RepositoryLocalSettingsStorage( load: { try self.load($0) }, save: { try self.save($0, at: $1) } ) }
func data(at url: URL) -> Data? { lock.lock() defer { lock.unlock() } return dataByURL[url] }
func save(_ data: Data, at url: URL) throws { lock.lock() defer { lock.unlock() } dataByURL[url] = data }
private func load(_ url: URL) throws -> Data { lock.lock() defer { lock.unlock() } guard let data = dataByURL[url] else { throw RepositoryLocalSettingsStorageError.missing } return data }}