native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
17 kB · 523 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524import ComposableArchitectureimport Dependenciesimport Foundationimport Sharingimport Testing
@testable import supacode
@MainActorstruct RepositoriesFeatureIconDetectionTests { // MARK: - Fixtures
private func makeWorktree(id: String, name: String, repoRoot: String) -> Worktree { Worktree( id: id, name: name, detail: "detail", workingDirectory: URL(fileURLWithPath: id), repositoryRootURL: URL(fileURLWithPath: repoRoot), createdAt: nil ) }
private func makeRepository( id: String, name: String = "repo", kind: Repository.Kind = .git, worktrees: [Worktree] = [], workspace: ProjectWorkspace? = nil ) -> Repository { Repository( id: id, rootURL: URL(fileURLWithPath: id), name: name, kind: kind, worktrees: IdentifiedArray(uniqueElements: worktrees), workspace: workspace ) }
private func makeState(repositories: [Repository]) -> RepositoriesFeature.State { var state = RepositoriesFeature.State() state.repositories = IdentifiedArray(uniqueElements: repositories) state.repositoryRoots = repositories.map(\.rootURL) state.isInitialLoadComplete = true state.snapshotPersistencePhase = .active return state }
private func recordingAssetStore( imported: LockIsolated<[URL]> = LockIsolated([]), removed: LockIsolated<[String]> = LockIsolated([]), importedFilename: String = "detected.png" ) -> RepositoryIconAssetStore { RepositoryIconAssetStore( importImage: { source, _ in imported.withValue { $0.append(source) } return importedFilename }, remove: { filename, _ in removed.withValue { $0.append(filename) } }, exists: { _, _ in true } ) }
private func appearances() -> [Repository.ID: RepositoryAppearance] { @Shared(.repositoryAppearances) var appearances return appearances }
// MARK: - Spawn eligibility
@Test func newRepositoryAfterInitialLoadIsScannedAndCommitted() async { let newRepo = makeRepository(id: "/tmp/new", name: "new", kind: .plain) let scanned = LockIsolated<[URL]>([]) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { root in scanned.withValue { $0.append(root) } return RepositoryIconCandidate( imageURL: root.appending(path: "logo.png"), evidence: .webAsset ) } $0.repositoryIconAssetStore = recordingAssetStore() $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off
await store.send( .repositoryManagement( .openRepositoriesFinished( [newRepo], failures: [], invalidRoots: [], openFailures: [], roots: [newRepo.rootURL] ) ) ) await store.receive(\.repositoryManagement.repositoryIconDetected) await store.finish()
#expect(scanned.value == [newRepo.rootURL]) #expect(appearances()["/tmp/new"]?.icon == .detectedImage(filename: "detected.png")) }
@Test func existingRepositoriesAreNotRescanned() async { let existing = makeRepository(id: "/tmp/existing", name: "existing") let scanned = LockIsolated<[URL]>([]) let store = TestStore(initialState: makeState(repositories: [existing])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { root in scanned.withValue { $0.append(root) } return nil } $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off
await store.send( .repositoryManagement( .openRepositoriesFinished( [existing], failures: [], invalidRoots: [], openFailures: [], roots: [existing.rootURL] ) ) ) await store.finish()
#expect(scanned.value.isEmpty) }
@Test func disabledSettingPreventsScan() async { let newRepo = makeRepository(id: "/tmp/new", name: "new", kind: .plain) let scanned = LockIsolated<[URL]>([]) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { root in scanned.withValue { $0.append(root) } return nil } $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off @Shared(.settingsFile) var settingsFile $settingsFile.withLock { $0.global.detectRepositoryIconsAutomatically = false }
await store.send( .repositoryManagement( .openRepositoriesFinished( [newRepo], failures: [], invalidRoots: [], openFailures: [], roots: [newRepo.rootURL] ) ) ) await store.finish()
#expect(scanned.value.isEmpty) }
@Test func suppressedOrAlreadyIconedRepositoriesAreSkipped() async { let suppressed = makeRepository(id: "/tmp/suppressed", name: "suppressed", kind: .plain) let iconed = makeRepository(id: "/tmp/iconed", name: "iconed", kind: .plain) let fresh = makeRepository(id: "/tmp/fresh", name: "fresh", kind: .plain) let scanned = LockIsolated<[URL]>([]) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { root in scanned.withValue { $0.append(root) } return nil } $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/suppressed"] = RepositoryAppearance(iconDetectionSuppressed: true) $0["/tmp/iconed"] = RepositoryAppearance(icon: .sfSymbol("folder")) }
await store.send( .repositoryManagement( .openRepositoriesFinished( [suppressed, iconed, fresh], failures: [], invalidRoots: [], openFailures: [], roots: [suppressed.rootURL, iconed.rootURL, fresh.rootURL] ) ) ) await store.finish()
#expect(scanned.value == [fresh.rootURL]) }
// MARK: - Commit guards
@Test func detectionResultCommitsForUntouchedRepository() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore() }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/repo", filename: "detected.png")) ) await store.finish()
#expect(appearances()["/tmp/repo"]?.icon == .detectedImage(filename: "detected.png")) }
@Test func manualIconSetDuringScanDiscardsResult() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) } @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance(icon: .sfSymbol("hammer")) }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/repo", filename: "detected.png")) ) await store.finish()
#expect(removed.value == ["detected.png"]) #expect(appearances()["/tmp/repo"]?.icon == .sfSymbol("hammer")) }
@Test func explicitClearDuringScanDiscardsResult() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) } @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance(iconDetectionSuppressed: true) }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/repo", filename: "detected.png")) ) await store.finish()
#expect(removed.value == ["detected.png"]) #expect(appearances()["/tmp/repo"]?.icon == nil) }
@Test func removedRepositoryDiscardsResult() async { let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/gone", filename: "detected.png")) ) await store.finish()
#expect(removed.value == ["detected.png"]) #expect(appearances()["/tmp/gone"] == nil) }
@Test func disabledSettingAtCommitTimeDiscardsResult() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) } @Shared(.settingsFile) var settingsFile $settingsFile.withLock { $0.global.detectRepositoryIconsAutomatically = false }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/repo", filename: "detected.png")) ) await store.finish()
#expect(removed.value == ["detected.png"]) #expect(appearances()["/tmp/repo"] == nil) }
@Test func commitPreservesExistingColor() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore() } @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance(icon: nil, color: .blue) }
await store.send( .repositoryManagement(.repositoryIconDetected("/tmp/repo", filename: "detected.png")) ) await store.finish()
#expect(appearances()["/tmp/repo"]?.icon == .detectedImage(filename: "detected.png")) #expect(appearances()["/tmp/repo"]?.color == .blue) }
// MARK: - Removal cleanup
@Test func repositoryRemovalCleansDetectedIconAndSuppressionButKeepsManualAppearance() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) $0.repositoryPersistence.loadRepositoryEntries = { [] } $0.repositoryPersistence.saveRepositoryEntries = { _ in } } store.exhaustivity = .off @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance( icon: .detectedImage(filename: "detected.png"), color: .blue ) }
await store.send( .repositoryManagement(.repositoryRemoved("/tmp/repo", selectionWasRemoved: false)) ) await store.finish()
#expect(removed.value == ["detected.png"]) #expect(appearances()["/tmp/repo"]?.icon == nil) #expect(appearances()["/tmp/repo"]?.color == .blue) #expect(appearances()["/tmp/repo"]?.iconDetectionSuppressed == false) }
@Test func repositoryRemovalClearsSuppressionOnlyEntryEntirely() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore() $0.repositoryPersistence.loadRepositoryEntries = { [] } $0.repositoryPersistence.saveRepositoryEntries = { _ in } } store.exhaustivity = .off @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance(iconDetectionSuppressed: true) }
await store.send( .repositoryManagement(.repositoryRemoved("/tmp/repo", selectionWasRemoved: false)) ) await store.finish()
#expect(appearances()["/tmp/repo"] == nil) }
@Test func repositoryRemovalKeepsManualUserImageUntouched() async { let repo = makeRepository(id: "/tmp/repo", name: "repo", kind: .plain) let removed = LockIsolated<[String]>([]) let store = TestStore(initialState: makeState(repositories: [repo])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconAssetStore = recordingAssetStore(removed: removed) $0.repositoryPersistence.loadRepositoryEntries = { [] } $0.repositoryPersistence.saveRepositoryEntries = { _ in } } store.exhaustivity = .off @Shared(.repositoryAppearances) var shared $shared.withLock { $0["/tmp/repo"] = RepositoryAppearance(icon: .userImage(filename: "mine.png")) }
await store.send( .repositoryManagement(.repositoryRemoved("/tmp/repo", selectionWasRemoved: false)) ) await store.finish()
#expect(removed.value.isEmpty) #expect(appearances()["/tmp/repo"]?.icon == .userImage(filename: "mine.png")) }
@Test func globalOptOutCancelsInFlightScans() async { let newRepo = makeRepository(id: "/tmp/new", name: "new", kind: .plain) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { _ in // Parks until cancelled, standing in for a long scan. let (stream, _) = AsyncStream<Never>.makeStream() for await _ in stream {} return nil } $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off
await store.send( .repositoryManagement( .openRepositoriesFinished( [newRepo], failures: [], invalidRoots: [], openFailures: [], roots: [newRepo.rootURL] ) ) ) await store.send(.repositoryManagement(.cancelPendingIconDetections)) await store.finish()
#expect(appearances()["/tmp/new"] == nil) }
@Test func detectorOwnedTemporaryFileIsDeletedAfterImport() async throws { let temp = FileManager.default.temporaryDirectory .appending(path: "icon-composer-artifact-\(UUID().uuidString).png") try Data([0xDE, 0xAD]).write(to: temp) let newRepo = makeRepository(id: "/tmp/new", name: "new", kind: .plain) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { _ in RepositoryIconCandidate( imageURL: temp, evidence: .appleIconComposer, ownsImageFile: true ) } $0.repositoryIconAssetStore = recordingAssetStore() $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off
await store.send( .repositoryManagement( .openRepositoriesFinished( [newRepo], failures: [], invalidRoots: [], openFailures: [], roots: [newRepo.rootURL] ) ) ) await store.receive(\.repositoryManagement.repositoryIconDetected) await store.finish()
#expect(!FileManager.default.fileExists(atPath: temp.path(percentEncoded: false))) #expect(appearances()["/tmp/new"]?.icon == .detectedImage(filename: "detected.png")) }
// MARK: - Workspace exclusion
@Test func workspaceContainersAreNeverScanned() async { let workspace = ProjectWorkspace(title: "WS") let container = makeRepository( id: "/tmp/workspace", name: "workspace", kind: .plain, workspace: workspace ) let scanned = LockIsolated<[URL]>([]) let store = TestStore(initialState: makeState(repositories: [])) { RepositoriesFeature() } withDependencies: { $0.repositoryIconDetector.detect = { root in scanned.withValue { $0.append(root) } return nil } $0.repositoryPersistence.saveRepositorySnapshot = { _ in } $0.gitClient.repositoryWebURL = { _ in nil } } store.exhaustivity = .off
await store.send( .repositoryManagement( .openRepositoriesFinished( [container], failures: [], invalidRoots: [], openFailures: [], roots: [container.rootURL] ) ) ) await store.finish()
#expect(scanned.value.isEmpty) }}