diff --git a/packages/factory-sdk/src/cli/fleet.test.ts b/packages/factory-sdk/src/cli/fleet.test.ts index 3fd007c2..4886da83 100644 --- a/packages/factory-sdk/src/cli/fleet.test.ts +++ b/packages/factory-sdk/src/cli/fleet.test.ts @@ -122,6 +122,86 @@ describe('fleet CLI parsing', () => { }) describe('fleet CLI runtime', () => { + it('uses real fleet and cloud mount for fixture-less factory configs on the operator path', async () => { + const root = await mkdtemp(join(tmpdir(), 'fleet-cli-real-default-')) + try { + const configPath = await writeConfig(root) + const realFleet = new FakeFleetClient() + const realMount = new FakeMountClient({ [issuePath]: issueFile }) + const createFleetCalls: unknown[] = [] + const cloudMountCalls: unknown[] = [] + const output = buffer() + + const code = await runFleetCli([ + 'factory', + 'run-once', + '--dry-run', + '--config', + configPath, + ], { + createFleet: (opts) => { + createFleetCalls.push(opts) + return realFleet + }, + cloudMountFromConfig: async (opts) => { + cloudMountCalls.push(opts) + return realMount + }, + stdout: output, + stderr: buffer(), + }) + + expect(code).toBe(0) + expect(createFleetCalls).toHaveLength(1) + expect(cloudMountCalls).toHaveLength(1) + const report = JSON.parse(output.text()) + expect(report).toMatchObject({ + dryRun: true, + pulled: [{ key: 'AR-77' }], + dispatched: [{ issue: { key: 'AR-77' } }], + }) + } finally { + await rm(root, { recursive: true, force: true }) + } + }) + + it('keeps explicit fixtureFiles configs on Fake fleet and mount for harness runs', async () => { + const root = await mkdtemp(join(tmpdir(), 'fleet-cli-fixture-opt-in-')) + try { + const configPath = await writeConfig(root, { + fixtureFiles: { [issuePath]: issueFile }, + }) + const output = buffer() + + const code = await runFleetCli([ + 'factory', + 'run-once', + '--dry-run', + '--config', + configPath, + ], { + createFleet: () => { + throw new Error('real fleet should not be selected for fixture config') + }, + cloudMountFromConfig: async () => { + throw new Error('real mount should not be selected for fixture config') + }, + stdout: output, + stderr: buffer(), + }) + + expect(code).toBe(0) + const report = JSON.parse(output.text()) + expect(report).toMatchObject({ + dryRun: true, + pulled: [{ key: 'AR-77' }], + dispatched: [{ issue: { key: 'AR-77' } }], + }) + } finally { + await rm(root, { recursive: true, force: true }) + } + }) + it('runs factory run-once dry-run over FleetClient and MountClient fakes with zero writes and zero spawns', async () => { const fleet = new FakeFleetClient() const mount = new FakeMountClient({ [issuePath]: issueFile }) diff --git a/packages/factory-sdk/src/cli/fleet.ts b/packages/factory-sdk/src/cli/fleet.ts index dbc4172d..e13014d9 100644 --- a/packages/factory-sdk/src/cli/fleet.ts +++ b/packages/factory-sdk/src/cli/fleet.ts @@ -20,12 +20,15 @@ import { type FleetClient, type MountClient, type ProbeCloser, + type RelayfileCloudMountClientConfig, } from '../index' import { FakeFleetClient, FakeMountClient } from '../testing' interface FleetCliDeps { fleet?: FleetClient mount?: MountClient + createFleet?: typeof createFleet + cloudMountFromConfig?: (config?: RelayfileCloudMountClientConfig) => Promise stdout?: Pick stderr?: Pick probeCloser?: ProbeCloser @@ -274,14 +277,14 @@ async function loadConfig(path?: string): Promise { const record = asRecord(raw) return { config: FactoryConfigSchema.parse(record.factoryConfig ?? record), - fixtureFiles: asRecord(record.fixtureFiles), + fixtureFiles: record.fixtureFiles ? asRecord(record.fixtureFiles) : undefined, } } async function buildFleet(globals: GlobalOptions, loaded: LoadedConfig | undefined, deps: FleetCliDeps): Promise { if (deps.fleet) return deps.fleet - if (globals.backend === 'internal' && loaded?.fixtureFiles) return new FakeFleetClient() - return createFleet({ + if (globals.backend === 'internal' && hasExplicitFixtureFiles(loaded)) return new FakeFleetClient() + return (deps.createFleet ?? createFleet)({ backend: globals.backend, cwd: process.cwd(), connectionPath: resolveBrokerConnectionPath(process.cwd()), @@ -305,15 +308,18 @@ export function resolveBrokerConnectionPath(startCwd = process.cwd()): string | async function buildMount(loaded: LoadedConfig, deps: FleetCliDeps): Promise { if (deps.mount) return deps.mount - if (loaded.fixtureFiles) return new FakeMountClient(loaded.fixtureFiles) + if (hasExplicitFixtureFiles(loaded)) return new FakeMountClient(loaded.fixtureFiles) let mount: MountClient - mount = await RelayfileCloudMountClient.fromConfig({ + mount = await (deps.cloudMountFromConfig ?? RelayfileCloudMountClient.fromConfig)({ workspaceId: loaded.config.workspaceId, isAllowedDraft: (path, content, opts) => isAllowedFactoryDraft(path, content, opts, mount, loaded.config), }) return mount } +const hasExplicitFixtureFiles = (loaded: LoadedConfig | undefined): loaded is LoadedConfig & { fixtureFiles: Record } => + loaded?.fixtureFiles !== undefined + async function isAllowedFactoryDraft( path: string, content: unknown,