Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
import { spawn, spawnSync } from 'child_process'
import { createServer, type Server } from 'http'
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from 'fs'
import { tmpdir } from 'os'
import { 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)) {
// http.Server#close() only stops NEW connections — it waits forever for any keep-alive socket the
// CLI's own `fetch` left open (undici pools connections for reuse) unless those are force-closed too.
server.closeAllConnections?.()
await new Promise<void>((resolve) => server.close(() => resolve()))
}
})
function tempRoot(): string {
const root = mkdtempSync(join(tmpdir(), 'harness-cli-auth-'))
dirs.push(root)
return root
}
function seedSession(root: string, opts: { expiresInMs?: number } = {}): 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() + (opts.expiresInMs ?? 60 * 60_000), // an hour out — accessToken() must not attempt a refresh
autonomousEnv: 'prod',
computerId: 'a'.repeat(32),
machineId: 'm_seeded',
updatedAt: Date.now(),
}))
}
function envFor(root: string, backendUrl?: string): NodeJS.ProcessEnv {
return {
...process.env,
HOME: root,
HARNESS_AUTH_DIR: join(root, 'auth'),
ADAPTER_DATA_DIR: join(root, 'data'),
ADAPTER_RUNTIME_DIR: join(root, 'runtime'),
ADAPTER_CLI_DIR: join(root, 'cli'),
ADAPTER_COMPUTER_ID_FILE: join(root, 'computer-id'),
ADAPTER_UPDATE_DISABLE: 'true',
// Sign-in should exercise the fake backend, never a developer's Grid binary or its installer.
HARNESS_GRID_BIN: join(root, 'grid-unavailable'),
DISABLE_GRID_INSTALL: 'true',
...(backendUrl ? { BACKEND_WS_URL: backendUrl } : {}),
}
}
function runSync(root: string, args: string[], backendUrl?: string) {
return spawnSync(process.execPath, [TSX, CLI_SOURCE, ...args], {
cwd: CLI_ROOT,
encoding: 'utf8',
env: envFor(root, backendUrl),
})
}
/** Async spawn — REQUIRED (not spawnSync) whenever the child talks back to a fake backend hosted in
* this same test process: spawnSync blocks this process's entire event loop until the child exits,
* so a fake server living here could never answer the child's request and the pair would deadlock. */
function runAsync(root: string, args: string[], backendUrl?: string): Promise<{ status: number | null; stdout: string; stderr: string }> {
return new Promise((resolve) => {
const child = spawn(process.execPath, [TSX, CLI_SOURCE, ...args], { cwd: CLI_ROOT, env: envFor(root, backendUrl) })
let stdout = ''
let stderr = ''
child.stdout.on('data', (c: Buffer) => { stdout += c.toString() })
child.stderr.on('data', (c: Buffer) => { stderr += c.toString() })
child.once('exit', (status) => resolve({ status, stdout, stderr }))
})
}
/** A minimal fake backend for the two `/api/auth/*` + resolve-computer calls `harness login`/
* `auth status` make — real network shape, canned answers, so the CLI's own code runs unmodified. */
function fakeBackend(handlers: {
authorizeNative?: (body: any) => any
exchange?: (body: any) => any
resolveComputer?: (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 }
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}` })
})
})
}
describe('harness auth status --json', () => {
it('reports loggedIn:false with no saved session, and never touches the network', () => {
const root = tempRoot()
const result = runSync(root, ['auth', 'status', '--json'], 'http://127.0.0.1:1') // port 1: would fail fast if ever called
expect(result.status).toBe(0)
// The computer id travels even signed out: the daemon serves this computer under it until a
// sign-in hands out a machineId, and the app keys the local machine by whichever it is told.
const body = JSON.parse(result.stdout.trim()) as { loggedIn: boolean; computerId?: string }
expect(body.loggedIn).toBe(false)
expect(body.computerId).toMatch(/^[0-9a-f-]{36}$/)
})
it('falls back to human text without --json', () => {
const root = tempRoot()
const result = runSync(root, ['auth', 'status'])
expect(result.status).toBe(0)
expect(result.stdout).toContain('Not signed in')
})
it('reports loggedIn:true from a saved, non-expiring session without a network round trip', () => {
const root = tempRoot()
seedSession(root)
const result = runSync(root, ['auth', 'status', '--json'], 'http://127.0.0.1:1')
expect(result.status).toBe(0)
expect(JSON.parse(result.stdout.trim())).toEqual({
loggedIn: true,
computerId: 'a'.repeat(32),
machineId: 'm_seeded',
autonomousEnv: 'prod',
expiresAt: expect.any(Number),
})
})
it('reports loggedIn:true and offline:true when the token needs a refresh the service cannot serve', () => {
// Near expiry forces a refresh; port 1 refuses it. That is a signed-in computer with no network —
// not a signed-out one. It used to read as loggedIn:false and send the desktop app to a login
// screen that could not have succeeded without the network either.
const root = tempRoot()
seedSession(root, { expiresInMs: 30_000 })
const result = runSync(root, ['auth', 'status', '--json'], 'http://127.0.0.1:1')
expect(result.status).toBe(0)
expect(JSON.parse(result.stdout.trim())).toMatchObject({ loggedIn: true, offline: true, machineId: 'm_seeded' })
})
})
describe('harness login --json', () => {
it('already-signed-in short-circuit emits a single success result line', async () => {
const root = tempRoot()
seedSession(root)
const { base } = await fakeBackend({
resolveComputer: () => ({ machine: { machineId: 'm_seeded' } }),
})
const result = await runAsync(root, ['login', '--json'], base)
expect(result.status).toBe(0)
const lines = result.stdout.trim().split('\n').map((l) => JSON.parse(l))
// The sign-in now also hands its token to `grid` and makes sure the account's private grid
// exists — best-effort, reported on this line rather than allowed to change its status. There is
// no `grid` on PATH in this test, so it reports the attempt and the harness sign-in still
// succeeds, which is the property worth pinning.
expect(lines).toEqual([{
type: 'result',
status: 'success',
alreadySignedIn: true,
grid: { signedIn: false, code: expect.any(String) },
}])
})
it('emits a BACKEND_ERROR result line (not a stack trace) when authorize-native is unreachable', async () => {
const root = tempRoot()
const result = await runAsync(root, ['login', '--json'], 'http://127.0.0.1:1')
expect(result.status).toBe(1)
const lines = result.stdout.trim().split('\n').filter(Boolean).map((l) => JSON.parse(l))
expect(lines).toEqual([{ type: 'result', status: 'error', code: 'BACKEND_ERROR', message: expect.any(String) }])
})
it('drives the full loopback flow: emits authorize_url, then a success result once the callback lands', 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, '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' })
expect(capturedRedirectUri).toMatch(/^http:\/\/127\.0\.0\.1:\d+\/callback$/)
// Simulate the browser completing SSO: hit this CLI's own loopback callback server.
await fetch(`${capturedRedirectUri}?code=code_123&state=state_456`)
const exitCode = await new Promise<number | null>((resolve) => child.once('exit', resolve))
expect(exitCode).toBe(0)
// Drain any trailing buffered line after exit.
if (stdout.trim()) lines.push(JSON.parse(stdout.trim()))
// Same contract as the already-signed-in line: the grid hand-off is reported here, and its
// failure (no `grid` on PATH in this test) never changes `status`.
expect(lines[1]).toEqual({
type: 'result',
status: 'success',
grid: { signedIn: false, code: expect.any(String) },
})
}, 15_000)
})
fc8f8511dThis file is pinned to the selected release. Download the original to open it in your design tool.