Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions packages/factory-sdk/src/cli/fleet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
16 changes: 11 additions & 5 deletions packages/factory-sdk/src/cli/fleet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<MountClient>
stdout?: Pick<NodeJS.WriteStream, 'write'>
stderr?: Pick<NodeJS.WriteStream, 'write'>
probeCloser?: ProbeCloser
Expand Down Expand Up @@ -274,14 +277,14 @@ async function loadConfig(path?: string): Promise<LoadedConfig> {
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<FleetClient> {
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()),
Expand All @@ -305,15 +308,18 @@ export function resolveBrokerConnectionPath(startCwd = process.cwd()): string |

async function buildMount(loaded: LoadedConfig, deps: FleetCliDeps): Promise<MountClient> {
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<string, unknown> } =>
loaded?.fixtureFiles !== undefined

async function isAllowedFactoryDraft(
path: string,
content: unknown,
Expand Down
Loading