native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210import Foundationimport ProwlCLISharedimport Testing
@testable import Prowl
struct WorkflowStartContextTests { static let autoFlow = """ schema: prowl.workflow/v1 id: auto-flow name: Auto Flow inputs: focus: { type: string, default: "" } roles: author: source: current reviewer: source: launch bind: auto steps: - id: brief message: author prompt: "Write about {{ inputs.focus }}." expect: { delivery: brief } - id: launch launch: reviewer prompt: "Read {{ deliveries.brief.path }}." """
static let worktreeOnly = """ schema: prowl.workflow/v1 id: worktree-only name: Worktree Only roles: runner: source: launch bind: auto steps: - id: launch launch: runner prompt: "Do the thing." """
private static let agentPane = WorkflowStartPaneCandidate( surfaceID: UUID(uuidString: "00000000-0000-0000-0000-000000000001")!, handle: "p1", agentToken: "claude", agentDisplayName: "Claude Code", paneTitle: "claude") private static let bareShellPane = WorkflowStartPaneCandidate( surfaceID: UUID(uuidString: "00000000-0000-0000-0000-000000000002")!, handle: "p2", agentToken: nil, agentDisplayName: nil, paneTitle: "zsh") private static let profileID = UUID(uuidString: "00000000-0000-0000-0000-000000000009")!
private func definition(_ yaml: String) throws -> WorkflowDefinition { try #require(WorkflowDocumentParser.parse(yaml).definition) }
private func context( yaml: String = autoFlow, source: WorkflowStartSource?, launchRoles: [WorkflowStartLaunchRole], pickRoles: [WorkflowStartPickRole] = [], cliInstalled: Bool = true, cliServiceFailure: String? = nil, validationFailure: String? = nil ) throws -> WorkflowStartContext { let definition = try definition(yaml) return WorkflowStartContext( item: WorkflowStartCatalogItem( key: "user/\(definition.id)", scope: .user, fileURL: URL(filePath: "/tmp/\(definition.id).yaml"), workflowID: definition.id, name: definition.name, workflowDescription: nil, icon: definition.icon, validationFailure: validationFailure), definition: definition, worktreeID: "/tmp/wt/", worktreeName: "main", source: source, launchRoles: launchRoles, pickRoles: pickRoles, cliInstalled: cliInstalled, bindModeOverride: nil, cliServiceFailure: cliServiceFailure) }
private func launchRole(bind: WorkflowBindMode = .auto, resolved: UUID? = profileID) -> WorkflowStartLaunchRole { WorkflowStartLaunchRole( name: "reviewer", effectiveBind: bind, resolvedProfileID: resolved, candidates: [], suggestion: nil, rejectedNote: nil) }
private func source(preselected: WorkflowStartPaneCandidate?) -> WorkflowStartSource { WorkflowStartSource( roleName: "author", candidates: [Self.agentPane, Self.bareShellPane], preselectedSurfaceID: preselected?.surfaceID) }
@Test func catalogItemCarriesTheWorkflowIcon() throws { let url = URL(filePath: "/tmp/icon.yaml") let file = WorkflowDiscovery.parse( Self.autoFlow.replacing("name: Auto Flow", with: "name: Auto Flow\nicon: bolt"), url: url, scope: .user, context: WorkflowValidationContext(scope: .user))
let item = try #require( WorkflowStartCatalogItem.make( entry: WorkflowCatalogEntry(file: file, shadowed: false), disabledWorkflowIDs: [], repositoryRootPath: nil))
#expect(item.scope == .user) #expect(item.fileURL == url) #expect(item.icon == "bolt") }
@Test func startsImmediatelyWhenNothingIsUndecided() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()]) #expect(context.canStartImmediately) }
@Test func askBindAlwaysPresentsTheSheet() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole(bind: .ask)]) #expect(!context.canStartImmediately) }
@Test func unresolvedAutoRolePresentsTheSheet() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole(resolved: nil)]) #expect(!context.canStartImmediately) }
@Test func pickRolesAreAlwaysAnExplicitChoice() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()], pickRoles: [WorkflowStartPickRole(name: "partner", candidates: [Self.agentPane])]) #expect(!context.canStartImmediately) }
@Test func defaultlessInputPresentsTheSheet() throws { let yaml = Self.autoFlow.replacing( "focus: { type: string, default: \"\" }", with: "focus: { type: string }") let context = try context( yaml: yaml, source: source(preselected: Self.agentPane), launchRoles: [launchRole()]) #expect(!context.canStartImmediately) }
@Test func theBannerActionFollowsTheInstallStatus() throws { var context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()], cliInstalled: false) #expect(context.cliInstallActionTitle == "Install") context.cliInstallStatus = .broken(path: "/usr/local/bin/prowl", destination: "/gone") #expect(context.cliInstallActionTitle == "Repair") context.cliInstallStatus = .installedDifferentSource( path: "/usr/local/bin/prowl", destination: "/other") #expect(context.cliInstallActionTitle == "Reinstall") context.cliInstallStatus = .installedDifferentSource( path: "/usr/local/bin/prowl", destination: nil) #expect(context.cliInstallActionTitle == nil) #expect(context.cliInstallBlockerCopy.contains("Remove it")) }
@Test func unreachableSocketPresentsTheSheet() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()], cliServiceFailure: "Another Prowl instance owns the socket.") #expect(!context.canStartImmediately) }
@Test func missingCLIPresentsTheSheet() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()], cliInstalled: false) #expect(!context.canStartImmediately) }
@Test func bareShellSourceNeedsTheSheetWhenAMessageDelivers() throws { let context = try context( source: source(preselected: Self.bareShellPane), launchRoles: [launchRole()]) #expect(!context.canStartImmediately) }
@Test func missingPreselectionPresentsTheSheet() throws { let context = try context(source: source(preselected: nil), launchRoles: [launchRole()]) #expect(!context.canStartImmediately) }
@Test func workflowWithoutACurrentRoleRunsAgainstTheWorktree() throws { let context = try context(yaml: Self.worktreeOnly, source: nil, launchRoles: [launchRole()]) #expect(context.canStartImmediately) }
@Test func invalidWorkflowNeverStartsImmediately() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()], validationFailure: "2 validation errors") #expect(!context.canStartImmediately) }
@Test func skipOptionsListEveryExpectCarryingStep() throws { let context = try context( source: source(preselected: Self.agentPane), launchRoles: [launchRole()]) #expect(context.skipOptions.map(\.stepID) == ["brief"]) }}