Files
oh-my-openagent/src/cli/run/json-output.test.ts
YeonGyu-Kim e343e625c7 feat(cli): extend run command with port, attach, session-id, on-complete, and json options
Implement all 5 CLI extension options for external orchestration:

- --port <port>: Start server on port, or attach if port occupied
- --attach <url>: Connect to existing opencode server
- --session-id <id>: Resume existing session instead of creating new
- --on-complete <command>: Execute shell command with env vars on completion
- --json: Output structured RunResult JSON to stdout

Refactor runner.ts into focused modules:
- agent-resolver.ts: Agent resolution logic
- server-connection.ts: Server connection management
- session-resolver.ts: Session create/resume with retry
- json-output.ts: Stdout redirect + JSON emission
- on-complete-hook.ts: Shell command execution with env vars

Fixes #1586
2026-02-07 17:26:33 +09:00

171 lines
4.6 KiB
TypeScript

import { describe, it, expect, beforeEach } from "bun:test"
import type { RunResult } from "./types"
import { createJsonOutputManager } from "./json-output"
interface MockWriteStream {
write: (chunk: string) => boolean
writes: string[]
}
function createMockWriteStream(): MockWriteStream {
const stream: MockWriteStream = {
writes: [],
write: function (this: MockWriteStream, chunk: string): boolean {
this.writes.push(chunk)
return true
},
}
return stream
}
describe("createJsonOutputManager", () => {
let mockStdout: MockWriteStream
let mockStderr: MockWriteStream
beforeEach(() => {
mockStdout = createMockWriteStream()
mockStderr = createMockWriteStream()
})
describe("redirectToStderr", () => {
it("causes stdout writes to go to stderr", () => {
// given
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
manager.redirectToStderr()
// when
mockStdout.write("test message")
// then
expect(mockStdout.writes).toHaveLength(0)
expect(mockStderr.writes).toEqual(["test message"])
})
})
describe("restore", () => {
it("reverses the redirect", () => {
// given
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
manager.redirectToStderr()
// when
manager.restore()
mockStdout.write("restored message")
// then
expect(mockStdout.writes).toEqual(["restored message"])
expect(mockStderr.writes).toHaveLength(0)
})
})
describe("emitResult", () => {
it("writes valid JSON to stdout", () => {
// given
const result: RunResult = {
sessionId: "test-session",
success: true,
durationMs: 1234,
messageCount: 42,
summary: "Test summary",
}
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
// when
manager.emitResult(result)
// then
expect(mockStdout.writes).toHaveLength(1)
const emitted = mockStdout.writes[0]!
expect(() => JSON.parse(emitted)).not.toThrow()
})
it("output matches RunResult schema", () => {
// given
const result: RunResult = {
sessionId: "test-session",
success: true,
durationMs: 1234,
messageCount: 42,
summary: "Test summary",
}
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
// when
manager.emitResult(result)
// then
const emitted = mockStdout.writes[0]!
const parsed = JSON.parse(emitted) as RunResult
expect(parsed).toEqual(result)
expect(parsed.sessionId).toBe("test-session")
expect(parsed.success).toBe(true)
expect(parsed.durationMs).toBe(1234)
expect(parsed.messageCount).toBe(42)
expect(parsed.summary).toBe("Test summary")
})
it("restores stdout even if redirect was active", () => {
// given
const result: RunResult = {
sessionId: "test-session",
success: true,
durationMs: 100,
messageCount: 1,
summary: "Test",
}
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
manager.redirectToStderr()
// when
manager.emitResult(result)
// then
expect(mockStdout.writes).toHaveLength(1)
expect(mockStdout.writes[0]!).toBe(JSON.stringify(result) + "\n")
mockStdout.write("after emit")
expect(mockStdout.writes).toHaveLength(2)
expect(mockStderr.writes).toHaveLength(0)
})
})
describe("multiple redirects and restores", () => {
it("work correctly", () => {
// given
const manager = createJsonOutputManager({
stdout: mockStdout as unknown as NodeJS.WriteStream,
stderr: mockStderr as unknown as NodeJS.WriteStream,
})
// when
manager.redirectToStderr()
mockStdout.write("first redirect")
manager.redirectToStderr()
mockStdout.write("second redirect")
manager.restore()
mockStdout.write("after restore")
// then
expect(mockStdout.writes).toEqual(["after restore"])
expect(mockStderr.writes).toEqual(["first redirect", "second redirect"])
})
})
})