Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
a8366f179This file is pinned to the selected release. Download the original to open it in your design tool.
/**
* The `grid` seam — the real CLI as a subprocess, against a fake backend and a fake `grid`.
*
* The fake `grid` is first on PATH and records the argv and the standard input it was handed. It
* **refuses** a `login` argv without `--harness`: asserting that the harness asked for the hand-off
* is the one thing a fake binary can honestly check, since it cannot exchange a token the way the
* real CLI does.
*
* Three commands meet here, and the last two are about what does NOT happen: `harness grid login`
* hands a token over, `harness grid logout` passes straight through to `grid logout` without
* reproducing any of it, and `harness logout` mentions the grid credential store without touching it.
*/
import { spawn } from 'child_process'
import { createServer, type Server } from 'http'
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
import { tmpdir } from 'os'
import { delimiter, join } from 'path'
import { fileURLToPath } from 'url'
import { afterEach, describe, expect, it } from 'vitest'
const CLI_ROOT = fileURLToPath(new URL('..', import.meta.url))
const CLI_SOURCE = join(CLI_ROOT, 'src', 'cli.ts')
const TSX = join(CLI_ROOT, 'node_modules', 'tsx', 'dist', 'cli.mjs')
const dirs: string[] = []
const servers: Server[] = []
afterEach(async () => {
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true })
for (const server of servers.splice(0)) {
server.closeAllConnections?.()
await new Promise<void>((resolve) => server.close(() => resolve()))
}
})
/** Records what it was given, then behaves as the surrounding test's env vars tell it to.
* Extensionless, so Node runs it as CommonJS regardless of any package.json above it. */
const FAKE_GRID = `#!/usr/bin/env node
'use strict'
const { writeFileSync } = require('fs')
const args = process.argv.slice(2)
function answer(stdin) {
writeFileSync(process.env.FAKE_GRID_RECORD, JSON.stringify({ args, stdin, env: process.env }))
// The guard is scoped to the verb it belongs to. Only the LOGIN hand-off must ask for
// \`--harness\`; \`grid logout\` has no such flag, and refusing it here would make the passthrough
// untestable against the same fake.
if (args[0] === 'login' && !args.includes('--harness')) {
process.stderr.write('fake grid: refusing argv that does not ask for the hand-off\\n')
process.exitCode = 64
return
}
if (process.env.FAKE_GRID_STDOUT) process.stdout.write(process.env.FAKE_GRID_STDOUT)
// Where the real \`grid\` puts every refusal — see the test that reads it back off the result line.
if (process.env.FAKE_GRID_STDERR) process.stderr.write(process.env.FAKE_GRID_STDERR)
process.exitCode = Number(process.env.FAKE_GRID_EXIT || '0')
}
// Standard input is read on the hand-off and NOWHERE else. The logout passthrough inherits stdin
// from the harness, whose own caller may never close it — so a fake that waited for end-of-file on
// every argv would hang the pair instead of measuring it.
if (args[0] === 'login') {
let stdin = ''
process.stdin.setEncoding('utf8')
process.stdin.on('data', (chunk) => { stdin += chunk })
process.stdin.on('end', () => answer(stdin))
} else {
answer('')
}
`
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'harness-cli-grid-'))
dirs.push(root)
return root
}
type GridOnPath = 'runnable' | 'absent' | 'not-executable'
/** A `grid` on PATH, in one of the three states the hand-off has to tell apart.
*
* `not-executable` is the one that earns its keep: it is the only case where the PATH pre-check and
* the spawn disagree — the check refuses it as unrunnable, while a spawn would answer EACCES, which
* is NOT the ENOENT the fallback recognises. Without it, deleting the pre-check entirely leaves the
* absent case green, and the test named for it would be measuring nothing of its own. */
function fakeGridBin(root: string, state: GridOnPath): string {
const dir = join(root, 'bin')
mkdirSync(dir, { recursive: true })
if (state === 'absent') return dir
const script = join(dir, 'grid')
writeFileSync(script, FAKE_GRID)
chmodSync(script, state === 'runnable' ? 0o755 : 0o644)
return dir
}
/** The same fake, somewhere PATH does not reach, for the case where the harness is TOLD where
* `grid` is. Its shebang names this process's Node by absolute path, because that case runs with
* an empty PATH — where `#!/usr/bin/env node` would fail for a reason that is not the one under
* test. */
function fakeGridOutsidePath(root: string): string {
const dir = join(root, 'elsewhere')
mkdirSync(dir, { recursive: true })
const script = join(dir, 'grid')
writeFileSync(script, FAKE_GRID.replace('#!/usr/bin/env node', `#!${process.execPath}`))
chmodSync(script, 0o755)
return script
}
function recordFile(root: string): string { return join(root, 'grid-invocation.json') }
function readRecord(root: string): { args: string[]; stdin: string; env: Record<string, string> } {
return JSON.parse(readFileSync(recordFile(root), 'utf8'))
}
function seedSession(root: string, overrides: Record<string, unknown> = {}): void {
const authDir = join(root, 'auth')
mkdirSync(authDir, { recursive: true })
writeFileSync(join(authDir, 'session.json'), JSON.stringify({
version: 1,
accessToken: 'tok_seeded',
refreshToken: 'refresh_seeded',
expiresAt: Date.now() + 60 * 60_000, // an hour out — accessToken() must not attempt a refresh
autonomousEnv: 'prod',
computerId: 'a'.repeat(32),
machineId: 'm_seeded',
updatedAt: Date.now(),
...overrides,
}))
}
function envFor(root: string, backendUrl?: string, extra: NodeJS.ProcessEnv = {}, grid: GridOnPath = 'runnable'): NodeJS.ProcessEnv {
// The harness resolves `grid` as HARNESS_GRID_BIN → the managed runtime → PATH (lib/gridExec.ts).
// The fake below is put on PATH, so the two answers that would outrank it are taken away: a
// developer's own override never reaches the child, and the runtime dir is one with no
// `current-grid` in it — not this machine's `~/.harness/runtime`.
const { HARNESS_GRID_BIN: _developersOwn, ...inherited } = process.env
return {
...inherited,
HOME: root,
HARNESS_AUTH_DIR: join(root, 'auth'),
ADAPTER_DATA_DIR: join(root, 'data'),
ADAPTER_CLI_DIR: join(root, 'cli'),
ADAPTER_RUNTIME_DIR: join(root, 'runtime'),
ADAPTER_COMPUTER_ID_FILE: join(root, 'computer-id'),
ADAPTER_UPDATE_DISABLE: 'true',
// The fake goes FIRST so it wins over any real `grid` this machine has. Unless it is meant to be
// unusable — then the rest of PATH is dropped entirely, or a developer's own installed `grid`
// answers and the test measures their machine instead of the refusal (it did: a real CLI
// predating `--harness` exited 2, which read as an outdated CLI rather than an absent one).
PATH: grid === 'runnable'
? `${fakeGridBin(root, grid)}${delimiter}${process.env.PATH ?? ''}`
: fakeGridBin(root, grid),
FAKE_GRID_RECORD: recordFile(root),
...(backendUrl ? { BACKEND_WS_URL: backendUrl } : {}),
...extra,
}
}
type Run = { status: number | null; stdout: string; stderr: string }
/** Async spawn — REQUIRED whenever the child talks to a fake backend hosted in THIS process, which
* spawnSync would block the event loop of until the child exits, deadlocking both.
*
* ⚠️ Resolves on **`close`**, never `exit`. `exit` fires while the child's stdio may still hold
* unemitted data, so a terminating NDJSON line written just before the process ends can be missing
* from `stdout` — and the cases below compare it for EQUALITY. A regression that DROPPED that line
* would produce the same shorter stdout, so the flake would make the real failure look like noise. */
function run(root: string, args: string[], backendUrl?: string, extra: NodeJS.ProcessEnv = {}, grid: GridOnPath = 'runnable'): Promise<Run> {
return new Promise((resolve) => {
const child = spawn(process.execPath, [TSX, CLI_SOURCE, ...args], {
cwd: CLI_ROOT,
env: envFor(root, backendUrl, extra, grid),
})
let stdout = ''
let stderr = ''
child.stdout.on('data', (c: Buffer) => { stdout += c.toString() })
child.stderr.on('data', (c: Buffer) => { stderr += c.toString() })
child.once('close', (status) => resolve({ status, stdout, stderr }))
})
}
function ndjson(stdout: string): Record<string, unknown>[] {
return stdout.trim().split('\n').filter(Boolean).map((line) => JSON.parse(line))
}
/** The `/api/auth/*` + resolve-computer calls the harness sign-in makes — real network shape. */
function fakeBackend(handlers: {
authorizeNative?: (body: any) => any
exchange?: (body: any) => any
resolveComputer?: (body: any) => any
refresh?: (body: any) => { status: number; body: unknown }
/** `POST /api/grid/name` — the account's minted grid name. Absent = an older backend, which is
* what every case that does not stub it is exercising (the route 404s and grid setup is skipped). */
gridName?: (body: any) => any
}): Promise<{ server: Server; base: string }> {
return new Promise((resolve) => {
const server = createServer((req, res) => {
let raw = ''
req.on('data', (c) => { raw += c })
req.on('end', () => {
const body = raw ? JSON.parse(raw) : {}
const send = (data: unknown): void => {
res.writeHead(200, { 'content-type': 'application/json' })
res.end(JSON.stringify({ success: true, data }))
}
if (req.url === '/api/auth/authorize-native' && handlers.authorizeNative) { send(handlers.authorizeNative(body)); return }
if (req.url === '/api/auth/exchange' && handlers.exchange) { send(handlers.exchange(body)); return }
if (req.url === '/api/machines/resolve-computer' && handlers.resolveComputer) { send(handlers.resolveComputer(body)); return }
if (req.url === '/api/grid/name' && handlers.gridName) { send(handlers.gridName(body)); return }
if (req.url === '/api/auth/refresh' && handlers.refresh) {
const answer = handlers.refresh(body)
res.writeHead(answer.status, { 'content-type': 'application/json' })
res.end(JSON.stringify(answer.body))
return
}
res.writeHead(404, { 'content-type': 'application/json' })
res.end(JSON.stringify({ success: false, error: { message: 'not stubbed' } }))
})
})
servers.push(server)
server.listen(0, '127.0.0.1', () => {
const address = server.address()
const port = typeof address === 'object' && address ? address.port : 0
resolve({ server, base: `http://127.0.0.1:${port}` })
})
})
}
const signedInBackend = () => fakeBackend({ resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }) })
describe('the fake grid itself', () => {
/** The positive row for the fake's own guard.
*
* Every case below asserts the argv the harness sent, and the fake refusing argv that omits
* `--harness` is what makes those assertions mean "the hand-off was asked for" rather than
* "some argv arrived". But the harness ALWAYS sends the flag, so nothing in this file ever
* drives that branch — a fake whose refusal had rotted away would look exactly like one that
* works, and the suite would go on reporting the seam as covered. So drive it directly. */
it('refuses argv that does not ask for the hand-off', async () => {
const root = tempRoot()
const grid = join(fakeGridBin(root, 'runnable'), 'grid')
const refused = await new Promise<number | null>((resolve) => {
const child = spawn(process.execPath, [grid, 'login', '--json'], {
env: { ...process.env, FAKE_GRID_RECORD: recordFile(root) },
})
child.stdin.end('a-token\n')
child.once('close', resolve)
})
expect(refused).toBe(64)
// And it still records what it was given, so a refusal is diagnosable rather than silent.
expect(readRecord(root).args).toEqual(['login', '--json'])
}, 20_000)
})
describe('harness grid login — an already-signed-in computer', () => {
it('hands the token over with no browser and one terminating result line', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base)
expect(result.status).toBe(0)
const lines = ndjson(result.stdout)
// No authorize_url anywhere: an existing session must never reopen the browser. And
// `alreadySignedIn` is the key `harness login --json` puts on exactly this outcome — the same
// contract, so a client driving the two does not need two readers.
expect(lines).toEqual([{ type: 'result', status: 'success', alreadySignedIn: true }])
expect(readRecord(root).args).toEqual(['login', '--harness', '--json'])
}, 20_000)
it('writes the token on standard input, and puts it in neither argv nor the environment', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
await run(root, ['grid', 'login', '--json'], base)
const record = readRecord(root)
expect(record.stdin.trim()).toBe('tok_seeded')
expect(JSON.stringify(record.args)).not.toContain('tok_seeded')
expect(JSON.stringify(record.env)).not.toContain('tok_seeded')
}, 20_000)
it('lets the child\'s own output through on the human path, and asks it for no JSON', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login'], base, { FAKE_GRID_STDOUT: 'Signed in as a@b.test.\n' })
expect(result.status).toBe(0)
expect(result.stdout).toContain('Signed in as a@b.test.')
expect(readRecord(root).args).toEqual(['login', '--harness'])
}, 20_000)
it('also makes sure the account\'s grid exists, without changing its result line', async () => {
// Signing in and stopping is what this command used to do, and it left an account whose grid had
// never been created signed in to nothing — an empty model picker naming no cause. The second
// half now runs here too, and the fake records the LAST `grid` it ran: anything other than
// `login` is proof the ensure happened.
const root = tempRoot()
seedSession(root)
const { base } = await fakeBackend({
resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }),
gridName: () => ({ gridName: 'someone-7f3a91c4' }),
})
const result = await run(root, ['grid', 'login', '--json'], base)
expect(result.status).toBe(0)
// The contract a client reads is untouched: the sign-in's outcome, and nothing about the ensure.
expect(ndjson(result.stdout)).toEqual([{ type: 'result', status: 'success', alreadySignedIn: true }])
expect(readRecord(root).args[0]).not.toBe('login')
}, 20_000)
it('leaves the grid alone when the backend mints no name — an older backend is not a failure', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend() // no `/api/grid/name` route
const result = await run(root, ['grid', 'login', '--json'], base)
expect(result.status).toBe(0)
expect(ndjson(result.stdout)).toEqual([{ type: 'result', status: 'success', alreadySignedIn: true }])
// Nothing after the hand-off, so the sign-in is still the last thing that ran.
expect(readRecord(root).args).toEqual(['login', '--harness', '--json'])
}, 20_000)
it('carries the child\'s JSON answer out on the result line', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base,
{ FAKE_GRID_STDOUT: '{"signed_in":true,"email":"a@b.test","grids":[]}\n' })
expect(result.status).toBe(0)
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'success', alreadySignedIn: true, grid: { signed_in: true, email: 'a@b.test', grids: [] } },
])
}, 20_000)
})
describe('harness grid login — a signed-out computer', () => {
it('runs the whole loopback sign-in first, then hands the new token over', async () => {
const root = tempRoot()
let capturedRedirectUri = ''
const { base } = await fakeBackend({
authorizeNative: (body) => { capturedRedirectUri = body.redirectUri; return { authorizeUrl: 'https://sso.example.test/authorize?tx=abc', tx: 'tx_abc' } },
exchange: () => ({ token: 'tok_new', refreshToken: 'refresh_new', expiresIn: 3600, autonomousEnv: 'prod' }),
resolveComputer: () => ({ machine: { machineId: 'm_new' } }),
})
const child = spawn(process.execPath, [TSX, CLI_SOURCE, 'grid', 'login', '--json'], {
cwd: CLI_ROOT,
env: envFor(root, base),
})
let stdout = ''
const lines: Record<string, unknown>[] = []
const gotUrl = new Promise<void>((resolve) => {
child.stdout.on('data', (chunk: Buffer) => {
stdout += chunk.toString()
while (stdout.includes('\n')) {
const idx = stdout.indexOf('\n')
const line = stdout.slice(0, idx).trim()
stdout = stdout.slice(idx + 1)
if (line) { lines.push(JSON.parse(line)); resolve() }
}
})
})
await gotUrl
expect(lines[0]).toEqual({ type: 'authorize_url', url: 'https://sso.example.test/authorize?tx=abc' })
// Simulate the browser completing SSO against this CLI's own loopback callback server.
await fetch(`${capturedRedirectUri}?code=code_123&state=state_456`)
// `close`, not `exit`: on `exit` the child's stdout may still hold the terminating result line.
const status = await new Promise<number | null>((resolve) => child.once('close', resolve))
if (stdout.trim()) lines.push(JSON.parse(stdout.trim()))
expect(status).toBe(0)
// Two lines total: the sign-in's authorize_url, and ONE terminating result — not one per half.
expect(lines).toEqual([
{ type: 'authorize_url', url: 'https://sso.example.test/authorize?tx=abc' },
// No `alreadySignedIn`: this computer was signed OUT, and the key is absent rather than
// `false` — which is how `harness login --json` words the same outcome.
{ type: 'result', status: 'success' },
])
expect(readRecord(root).stdin.trim()).toBe('tok_new')
}, 30_000)
})
describe('harness grid login — when the hand-off fails', () => {
it('propagates the child\'s exit code', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base, { FAKE_GRID_EXIT: '7' })
expect(result.status).toBe(7)
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'error', code: 'GRID_LOGIN_FAILED', message: expect.any(String) },
])
}, 20_000)
/** ⚠️ Not a shape today's `grid` produces: it refuses on STDERR (`cli/json_error.py` prints there,
* and a `SystemExit` string goes there too), and that stream is inherited, so a refusal already
* reaches the caller untouched. What this pins is the seam's own property — captured stdout is
* never SWALLOWED — which is what stops a child that does write there from vanishing under the
* one mode whose whole point is that a client can read the answer. */
it('does not swallow what the child put on stdout before failing', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base,
{ FAKE_GRID_EXIT: '7', FAKE_GRID_STDOUT: '{"error":{"message":"no such grid"}}\n' })
expect(result.status).toBe(7)
expect(ndjson(result.stdout)).toEqual([{
type: 'result',
status: 'error',
code: 'GRID_LOGIN_FAILED',
message: expect.any(String),
grid: { error: { message: 'no such grid' } },
}])
}, 20_000)
it('carries the child\'s own refusal out on the result line, and still to stderr', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
// The shape a real refusal has: `grid` writes its sentence to STDERR and nothing to stdout.
const refusal = 'This control plane cannot sign you in with an Autonomous account token yet.\n'
const result = await run(root, ['grid', 'login', '--json'], base,
{ FAKE_GRID_EXIT: '1', FAKE_GRID_STDERR: refusal })
// Needed in BOTH places, for two different readers. A client reading NDJSON off stdout would
// otherwise hold an exit code and have no sentence to show anybody — this command's own message
// classifies the failure, only the child's names the way out of it.
const [line] = ndjson(result.stdout)
expect(line).toMatchObject({ type: 'result', status: 'error', code: 'GRID_LOGIN_FAILED' })
expect(String(line.detail)).toContain('cannot sign you in')
// And a person watching the terminal still sees it where it has always been.
expect(result.stderr).toContain('cannot sign you in')
}, 20_000)
it('says nothing where the child said nothing, rather than sending empty keys', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base, { FAKE_GRID_EXIT: '7' })
// An ABSENT key reads as "the child said nothing there"; `null` or `''` would read as an answer.
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'error', code: 'GRID_LOGIN_FAILED', message: expect.any(String) },
])
}, 20_000)
it('reads exit code 2 as an outdated grid CLI, not as a network failure', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base, { FAKE_GRID_EXIT: '2' })
expect(result.status).toBe(2)
const [line] = ndjson(result.stdout)
expect(line).toMatchObject({ type: 'result', status: 'error', code: 'GRID_CLI_OUTDATED' })
expect(String(line.message)).toContain('too old')
}, 20_000)
it('says the same thing in the human text', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login'], base, { FAKE_GRID_EXIT: '2' })
expect(result.status).toBe(2)
expect(result.stderr).toContain('too old')
}, 20_000)
it('reports a missing grid CLI when there is none on PATH', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base, {}, 'absent')
expect(result.status).toBe(1)
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'error', code: 'GRID_CLI_MISSING', message: expect.any(String) },
])
}, 20_000)
it('reports a grid it cannot RUN as missing too, rather than as a failed sign-in', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const result = await run(root, ['grid', 'login', '--json'], base, {}, 'not-executable')
// The case above would stay green with the PATH pre-check deleted, because a spawn into an
// empty PATH answers ENOENT and the fallback maps that to the same code. This one would not: a
// present-but-unrunnable file is EACCES, which the fallback does NOT recognise, so without the
// pre-check it would surface as GRID_LOGIN_FAILED.
expect(result.status).toBe(1)
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'error', code: 'GRID_CLI_MISSING', message: expect.any(String) },
])
}, 20_000)
})
describe('harness grid login — which `grid` it runs', () => {
/** The hand-off carries the token, so it has to run the binary every other grid call resolves to
* (`gridExec.ts`: override → managed runtime → PATH), never a PATH lookup of its own. The
* resolution order itself is pinned in `gridHandoff.spec.ts`; what this drives through the real
* CLI is that the override is read off THIS process's environment, which is the seam a unit test
* of the module cannot see. */
it('runs the grid HARNESS_GRID_BIN names when PATH has none', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await signedInBackend()
const elsewhere = fakeGridOutsidePath(root)
const result = await run(root, ['grid', 'login', '--json'], base, { HARNESS_GRID_BIN: elsewhere }, 'absent')
expect(result.status).toBe(0)
expect(readRecord(root).args).toEqual(['login', '--harness', '--json'])
expect(readRecord(root).stdin.trim()).toBe('tok_seeded')
}, 20_000)
})
describe('harness grid login — when the harness session itself is broken', () => {
it('names the harness sign-in, not the grid one, when the refresh token is invalid', async () => {
const root = tempRoot()
seedSession(root, { expiresAt: Date.now() - 60_000 })
const { base } = await fakeBackend({
resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }),
refresh: () => ({ status: 401, body: { success: false, error: { code: 'REFRESH_TOKEN_INVALID', message: 'nope' } } }),
})
const result = await run(root, ['grid', 'login', '--json'], base)
expect(result.status).toBe(1)
const [line] = ndjson(result.stdout)
expect(line).toMatchObject({ type: 'result', status: 'error', code: 'AUTH_ERROR' })
expect(String(line.message)).toContain('harness login')
}, 20_000)
})
describe('harness grid login — the sign-in half it inherits', () => {
it('refreshes an expired token and hands over the NEW one, never the stale one', async () => {
const root = tempRoot()
seedSession(root, { expiresAt: Date.now() - 60_000 })
const { base } = await fakeBackend({
resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }),
refresh: () => ({ status: 200, body: { success: true, data: { token: 'tok_refreshed', refreshToken: 'refresh_2', expiresIn: 3600 } } }),
})
const result = await run(root, ['grid', 'login', '--json'], base)
// The whole of this command's answer to "the harness token expired" is that it asks the session
// manager rather than reading the file: a token read off disk would be `tok_seeded`, and the
// hand-off would trade a credential the control plane has already stopped accepting.
expect(result.status).toBe(0)
expect(readRecord(root).stdin.trim()).toBe('tok_refreshed')
}, 20_000)
it('--force reaches the sign-in, so a session on disk does not short-circuit it', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await fakeBackend({
authorizeNative: () => ({ authorizeUrl: 'https://sso.example.test/authorize?tx=forced', tx: 'tx_forced' }),
resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }),
})
const child = spawn(process.execPath, [TSX, CLI_SOURCE, 'grid', 'login', '--force', '--json'], {
cwd: CLI_ROOT,
env: envFor(root, base),
})
const first = await new Promise<Record<string, unknown>>((resolve) => {
let buffered = ''
child.stdout.on('data', (chunk: Buffer) => {
buffered += chunk.toString()
const idx = buffered.indexOf('\n')
if (idx >= 0) resolve(JSON.parse(buffered.slice(0, idx).trim()))
})
})
child.kill() // it would otherwise wait five minutes for a callback nothing is going to send
// A seeded session is present, so WITHOUT --force reaching loginCommand this would short-circuit
// and the first line would be the terminating result instead.
expect(first).toEqual({ type: 'authorize_url', url: 'https://sso.example.test/authorize?tx=forced' })
}, 20_000)
it('codes a backend failure in the sign-in half rather than crashing out of --json', async () => {
const root = tempRoot()
seedSession(root)
// Nothing stubbed: resolve-computer answers 404, the way a backend having a bad minute would.
const { base } = await fakeBackend({})
const result = await run(root, ['grid', 'login', '--json'], base)
// The stream must stay NDJSON. Unguarded this produced a stack trace and NO result line at all.
expect(result.status).toBe(1)
expect(ndjson(result.stdout)).toEqual([
{ type: 'result', status: 'error', code: 'BACKEND_ERROR', message: expect.any(String) },
])
expect(result.stderr).not.toContain('Failed to start adapter')
}, 20_000)
})
describe('the harness grid namespace', () => {
it('refuses a subcommand it does not have', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['grid', 'nonsense'], 'http://127.0.0.1:1')
expect(result.status).toBe(1)
expect(result.stderr).toContain('Unknown command: grid nonsense')
}, 20_000)
})
// ── signing out ────────────────────────────────────────────────────────────────────────────────
//
// Two verbs, and the tests below are mostly about the wall between them: `harness grid logout` runs
// the grid sign-out and nothing of the harness's, and `harness logout` mentions the grid credential
// store without reading a byte of it or deleting it.
/** The grid credential store, as `grid` itself lays it out: `<GRID_HOME>/credentials.toml`. */
function seedGridCredentials(dir: string): string {
mkdirSync(dir, { recursive: true })
const file = join(dir, 'credentials.toml')
writeFileSync(file, 'session_token = "grid_seeded"\n')
return file
}
/** `harness logout`, with GRID_HOME under this test's control rather than the developer's shell.
*
* A stray `GRID_HOME` in the ambient environment would point the check at a directory this test
* never wrote, and the SILENT case would then pass for entirely the wrong reason. Passing
* `undefined` drops the variable from the child's environment altogether, which is the state the
* `~/.grid` default is for. */
function runLogout(root: string, gridHome?: string, grid: GridOnPath = 'runnable'): Promise<Run> {
return run(root, ['logout'], 'http://127.0.0.1:1', { GRID_HOME: gridHome }, grid)
}
describe('harness grid logout', () => {
it('runs the grid sign-out and answers with the child\'s own exit code', async () => {
const root = tempRoot()
seedSession(root)
// Port 1 for the backend: nothing on this path may reach it. The grid sign-out is the grid's
// business, and a harness session this command never reads is one it can never be blocked by.
const result = await run(root, ['grid', 'logout'], 'http://127.0.0.1:1',
{ FAKE_GRID_STDOUT: 'Signed out. Removed credentials for 2 grids.\n' })
expect(result.status).toBe(0)
expect(result.stdout).toContain('Removed credentials for 2 grids.')
expect(readRecord(root).args).toEqual(['logout'])
}, 20_000)
it('carries a refusal out as a non-zero exit, in the child\'s own words', async () => {
const root = tempRoot()
seedSession(root)
// The shape `grid logout` refuses in: it will not delete credentials it may still need to
// deregister a serve child with, and it says so on stderr.
const refusal = 'dt-edge: still serving on pid 4242. Stop it, or sign out with --force.\n'
const result = await run(root, ['grid', 'logout'], 'http://127.0.0.1:1',
{ FAKE_GRID_EXIT: '1', FAKE_GRID_STDERR: refusal })
expect(result.status).toBe(1)
expect(result.stderr).toContain('still serving on pid 4242')
}, 20_000)
it('lets --force reach the child, so the override a person already knows still works', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['grid', 'logout', '--force'], 'http://127.0.0.1:1')
expect(result.status).toBe(0)
expect(readRecord(root).args).toEqual(['logout', '--force'])
}, 20_000)
it('passes --json through and hands back the child\'s answer unwrapped', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['grid', 'logout', '--json'], 'http://127.0.0.1:1',
{ FAKE_GRID_STDOUT: '{"signed_out":true,"grids":2}\n' })
expect(result.status).toBe(0)
expect(readRecord(root).args).toEqual(['logout', '--json'])
// ⚠️ EXACTLY the child's document, with no harness envelope around it and no result line after
// it. `harness grid login --json` has two halves to reconcile and therefore emits its own
// terminating line; this command has ONE, so anything it added would be a second dialect of an
// answer `grid logout` already gives.
expect(result.stdout.trim()).toBe('{"signed_out":true,"grids":2}')
}, 20_000)
it('never signs the harness out, not even when the grid sign-out refuses', async () => {
const root = tempRoot()
seedSession(root)
const session = join(root, 'auth', 'session.json')
const before = readFileSync(session, 'utf8')
const result = await run(root, ['grid', 'logout'], 'http://127.0.0.1:1', { FAKE_GRID_EXIT: '1' })
expect(result.status).toBe(1)
// No cascade in this direction either: a grid condition decides the exit code and nothing else.
expect(existsSync(session)).toBe(true)
expect(readFileSync(session, 'utf8')).toBe(before)
}, 20_000)
it('says there is no grid CLI rather than crashing on the spawn', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['grid', 'logout'], 'http://127.0.0.1:1', {}, 'absent')
expect(result.status).toBe(1)
// Named as the missing thing it is. `toContain('grid')` would be satisfied by the dispatcher's
// own "Unknown command: grid logout", which is what this said before the command existed.
expect(result.stderr).toContain('PATH')
expect(result.stderr).not.toContain('Unknown command')
expect(result.stderr).not.toContain('Failed to start adapter')
}, 20_000)
it('drops only the verb it consumed, never a later token that spells it', async () => {
const root = tempRoot()
seedSession(root)
// `grid logout` takes no option value TODAY, so this is the passthrough's stated contract rather
// than a flag that exists: "a flag `grid logout` grows tomorrow works here the day it ships".
// Filtering the argv by VALUE would forward `--reason` with its value eaten.
const result = await run(root, ['grid', 'logout', '--reason', 'logout'], 'http://127.0.0.1:1')
expect(result.status).toBe(0)
expect(readRecord(root).args).toEqual(['logout', '--reason', 'logout'])
}, 20_000)
it('reports a grid it cannot RUN as missing too', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['grid', 'logout'], 'http://127.0.0.1:1', {}, 'not-executable')
// This module keeps its OWN copy of the PATH pre-check, so the sibling case in the hand-off
// suite proves nothing about it. The case above would survive that pre-check being deleted here
// — an empty PATH answers ENOENT, which the spawn's own fallback maps to the same sentence. A
// present-but-unrunnable file answers EACCES, which it does not.
expect(result.status).toBe(1)
expect(result.stderr).toContain('PATH')
}, 20_000)
})
describe('harness logout — the grid sign-out it now performs', () => {
// ⚠️ This suite used to be named "the grid credential it will not touch" and asserted the
// OPPOSITE: that `harness logout` only ever printed a sentence about the grid. The one-sign-in
// flow reversed that decision deliberately — one sign-in creates the grid session, so one
// sign-out ends it — and these tests are what stop it being reverted by someone reading the old
// rule. The two objections the old rule was built on are each pinned below rather than dropped.
it('runs `grid logout` as part of signing out', async () => {
const root = tempRoot()
seedSession(root)
const result = await runLogout(root)
expect(result.status).toBe(0)
expect(result.stdout).toContain('Signed out.')
expect(readRecord(root).args).toEqual(['logout'])
}, 20_000)
it('completes the harness sign-out even when the grid refuses', async () => {
const root = tempRoot()
seedSession(root)
// `grid logout` refuses over a serve child it cannot confirm stopped. That must be reported and
// never propagated: a grid condition cannot block a harness sign-out.
const result = await run(root, ['logout'], 'http://127.0.0.1:1', {
FAKE_GRID_EXIT: '1',
FAKE_GRID_STDERR: 'dt-edge: still serving on pid 4242. Stop it, or sign out with --force.\n',
})
expect(result.status).toBe(0)
expect(result.stdout).toContain('Signed out.')
expect(result.stderr).toContain('still serving on pid 4242')
}, 20_000)
it('falls back to the old sentence when there is no `grid` to run at all', async () => {
const root = tempRoot()
seedSession(root)
seedGridCredentials(join(root, '.grid'))
const result = await runLogout(root, undefined, 'absent')
// Nothing ran, so a credential really is being left behind — which is the one case the warning
// was written for, and the only one where it is still true.
expect(result.status).toBe(0)
expect(result.stdout).toContain('Signed out.')
expect(result.stderr).toContain('harness grid logout')
}, 20_000)
it('says nothing about the grid when nothing ran and there was no credential either', async () => {
const root = tempRoot()
seedSession(root)
const result = await runLogout(root, undefined, 'absent')
expect(result.status).toBe(0)
expect(result.stderr).not.toContain('grid logout')
}, 20_000)
it('never deletes the grid credential file itself', async () => {
const root = tempRoot()
seedSession(root)
const credentials = seedGridCredentials(join(root, '.grid'))
const before = readFileSync(credentials, 'utf8')
await runLogout(root)
// Deleting credentials is `grid logout`'s own job, done in its own order (serve children first).
// The harness spawns it and reads nothing out of the store.
expect(existsSync(credentials)).toBe(true)
expect(readFileSync(credentials, 'utf8')).toBe(before)
}, 20_000)
it('looks where GRID_HOME points, the way `grid` itself does', async () => {
const root = tempRoot()
seedSession(root)
const elsewhere = join(root, 'grid-home')
seedGridCredentials(elsewhere)
// With no `grid` on PATH the fallback sentence fires, which is what makes the path resolution
// observable at all — the cascade itself never reads the store.
const result = await runLogout(root, elsewhere, 'absent')
expect(result.stderr).toContain('harness grid logout')
}, 20_000)
it('expands a leading ~ in GRID_HOME, because `grid` does', async () => {
const root = tempRoot()
seedSession(root)
// `envFor` sets HOME to the temp root, so `~` inside the child expands to there — seeding at
// the TEST process's home would be a different directory entirely.
seedGridCredentials(join(root, 'grid-state'))
const result = await runLogout(root, '~/grid-state', 'absent')
expect(result.stderr).toContain('harness grid logout')
}, 20_000)
})
describe('harness reset — the second door onto the same sign-out', () => {
it('warns about a grid sign-in too, and leaves it alone', async () => {
const root = tempRoot()
seedSession(root)
const credentials = seedGridCredentials(join(root, '.grid'))
const before = readFileSync(credentials, 'utf8')
// `reset` clears the SSO session exactly as `logout` does, and more besides — so somebody who
// runs it is if anything likelier to believe nothing is left behind. A warning written at one
// door and not the other is a warning the other silently does without.
const result = await run(root, ['reset'], 'http://127.0.0.1:1', { GRID_HOME: undefined })
expect(result.status).toBe(0)
expect(result.stderr).toContain('harness grid logout')
expect(existsSync(credentials)).toBe(true)
expect(readFileSync(credentials, 'utf8')).toBe(before)
}, 20_000)
it('stays silent when there is no grid sign-in', async () => {
const root = tempRoot()
seedSession(root)
const result = await run(root, ['reset'], 'http://127.0.0.1:1', { GRID_HOME: undefined })
expect(result.status).toBe(0)
expect(result.stderr).not.toContain('grid logout')
}, 20_000)
})