test: make unsafe test coercion explicit
Move test coercion out of a hidden global and require each test to import the helper so review tools and runtime scripts can see the unsafe boundary. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { afterEach, describe, expect, mock, test } from "bun:test"
|
||||
|
||||
import { fetchNpmDistTags } from "../config-manager"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
describe("fetchNpmDistTags", () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
@@ -13,7 +14,7 @@ describe("fetchNpmDistTags", () => {
|
||||
|
||||
test("returns dist-tags on success", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ latest: "3.13.1", beta: "3.14.0-beta.1" }),
|
||||
@@ -29,7 +30,7 @@ describe("fetchNpmDistTags", () => {
|
||||
|
||||
test("returns null on network failure", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() => Promise.reject(new Error("Network error"))))
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() => Promise.reject(new Error("Network error"))))
|
||||
|
||||
//#when
|
||||
const result = await fetchNpmDistTags("oh-my-openagent")
|
||||
@@ -40,7 +41,7 @@ describe("fetchNpmDistTags", () => {
|
||||
|
||||
test("returns null on non-ok response", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 404,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"
|
||||
|
||||
import * as configContext from "./config-context"
|
||||
import * as spawnHelpers from "../../shared/spawn-with-windows-hide"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
type OpenCodeBinaryModule = typeof import("./opencode-binary")
|
||||
|
||||
@@ -92,11 +93,11 @@ describe("getOpenCodeVersion (installer)", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
const immediateSetTimeout = testCoerce<typeof globalThis.setTimeout>(((handler: TimerHandler) => {
|
||||
const immediateSetTimeout = unsafeTestValue<typeof globalThis.setTimeout>(((handler: TimerHandler) => {
|
||||
if (typeof handler === "function") {
|
||||
handler()
|
||||
}
|
||||
return testCoerce<ReturnType<typeof setTimeout>>(1)
|
||||
return unsafeTestValue<ReturnType<typeof setTimeout>>(1)
|
||||
}))
|
||||
const setTimeoutSpy = spyOn(globalThis, "setTimeout").mockImplementation(immediateSetTimeout)
|
||||
|
||||
@@ -124,11 +125,11 @@ describe("getOpenCodeVersion (installer)", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
const immediateSetTimeout = testCoerce<typeof globalThis.setTimeout>(((handler: TimerHandler) => {
|
||||
const immediateSetTimeout = unsafeTestValue<typeof globalThis.setTimeout>(((handler: TimerHandler) => {
|
||||
if (typeof handler === "function") {
|
||||
handler()
|
||||
}
|
||||
return testCoerce<ReturnType<typeof setTimeout>>(1)
|
||||
return unsafeTestValue<ReturnType<typeof setTimeout>>(1)
|
||||
}))
|
||||
const setTimeoutSpy = spyOn(globalThis, "setTimeout").mockImplementation(immediateSetTimeout)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { afterEach, describe, expect, mock, test } from "bun:test"
|
||||
|
||||
import { getPluginNameWithVersion } from "../config-manager"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
describe("getPluginNameWithVersion", () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
@@ -13,7 +14,7 @@ describe("getPluginNameWithVersion", () => {
|
||||
|
||||
test("returns the canonical latest tag when current version matches latest", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ latest: "3.13.1", beta: "3.14.0-beta.1" }),
|
||||
@@ -29,7 +30,7 @@ describe("getPluginNameWithVersion", () => {
|
||||
|
||||
test("preserves the canonical prerelease channel when fetch fails", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() => Promise.reject(new Error("Network error"))))
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() => Promise.reject(new Error("Network error"))))
|
||||
|
||||
//#when
|
||||
const result = await getPluginNameWithVersion("3.14.0-beta.1")
|
||||
@@ -40,7 +41,7 @@ describe("getPluginNameWithVersion", () => {
|
||||
|
||||
test("returns the canonical bare package name for stable fallback", async () => {
|
||||
//#given
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: false,
|
||||
status: 404,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { join } from "node:path"
|
||||
import { install } from "./install"
|
||||
import * as configManager from "./config-manager"
|
||||
import type { InstallArgs } from "./types"
|
||||
import { unsafeTestValue } from "../../test-support/unsafe-test-value"
|
||||
|
||||
// Mock console methods to capture output
|
||||
const mockConsoleLog = mock(() => {})
|
||||
@@ -57,7 +58,7 @@ describe("install CLI - binary check behavior", () => {
|
||||
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue(null)
|
||||
|
||||
// given mock npm fetch
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ latest: "3.0.0" }),
|
||||
@@ -92,7 +93,7 @@ describe("install CLI - binary check behavior", () => {
|
||||
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue(null)
|
||||
|
||||
// given mock npm fetch
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ latest: "3.0.0" }),
|
||||
@@ -131,7 +132,7 @@ describe("install CLI - binary check behavior", () => {
|
||||
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.4.0")
|
||||
|
||||
// given mock npm fetch
|
||||
globalThis.fetch = testCoerce<typeof fetch>(mock(() =>
|
||||
globalThis.fetch = unsafeTestValue<typeof fetch>(mock(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ latest: "3.0.0" }),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os"
|
||||
import type { RunContext } from "./types"
|
||||
import { _resetForTesting, setSessionAgent } from "../../features/claude-code-session-state"
|
||||
import { writeState as writeRalphLoopState } from "../../hooks/ralph-loop/storage"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const testDirs: string[] = []
|
||||
|
||||
@@ -26,7 +27,7 @@ function createTempDir(): string {
|
||||
|
||||
function createMockContext(directory: string): RunContext {
|
||||
return {
|
||||
client: testCoerce<RunContext["client"]>({
|
||||
client: unsafeTestValue<RunContext["client"]>({
|
||||
session: {
|
||||
todo: mock(() => Promise.resolve({ data: [] })),
|
||||
children: mock(() => Promise.resolve({ data: [] })),
|
||||
@@ -155,13 +156,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "child-session"
|
||||
setSessionAgent("child-session", "atlas")
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "child-session" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "child-session"
|
||||
? [{ info: { agent: "atlas", providerID: "openai", modelID: "gpt-5.4" } }]
|
||||
: [],
|
||||
@@ -187,13 +188,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "lineage-only-session"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "lineage-only-session" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async () => ({ data: [] })))
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async () => ({ data: [] })))
|
||||
|
||||
const { checkCompletionConditions } = await import("./completion")
|
||||
|
||||
@@ -218,13 +219,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "mismatch-subagent-session"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "mismatch-subagent-session" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "mismatch-subagent-session"
|
||||
? [{ info: { agent: "sisyphus-junior", providerID: "openai", modelID: "gpt-5.4" } }]
|
||||
: [],
|
||||
@@ -253,13 +254,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "appended-mismatch-session"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "appended-mismatch-session" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "appended-mismatch-session"
|
||||
? [{ info: { agent: "sisyphus-junior", providerID: "openai", modelID: "gpt-5.4" } }]
|
||||
: [],
|
||||
@@ -288,10 +289,10 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_appended_descendant"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async () => {
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async () => {
|
||||
throw new Error("session lookup failed")
|
||||
}))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "ses_appended_descendant"
|
||||
? [{ info: { agent: "atlas", providerID: "openai", modelID: "gpt-5.4" } }]
|
||||
: [],
|
||||
@@ -317,7 +318,7 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_direct_child"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_direct_child" ? "ses_parent" : undefined,
|
||||
@@ -347,7 +348,7 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_direct_tracked"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: undefined,
|
||||
@@ -374,7 +375,7 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_unknown_child"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async () => {
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async () => {
|
||||
throw new Error("lineage unavailable")
|
||||
}))
|
||||
|
||||
@@ -401,13 +402,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_direct_child"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_direct_child" ? "ses_root_tracked" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "ses_direct_child"
|
||||
? [{ info: { agent: "sisyphus-junior", providerID: "openai", modelID: "gpt-5.4" } }]
|
||||
: [],
|
||||
@@ -437,13 +438,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_child_after_compaction"
|
||||
setSessionAgent("ses_child_after_compaction", "atlas")
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_child_after_compaction" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "ses_child_after_compaction"
|
||||
? [
|
||||
{ info: { agent: "atlas", providerID: "openai", modelID: "gpt-5.4" } },
|
||||
@@ -472,13 +473,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_sqlite_descendant"
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_sqlite_descendant" ? "root-session" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: path.id === "ses_sqlite_descendant"
|
||||
? [
|
||||
{ id: "msg_0001", info: { agent: "atlas", providerID: "openai", modelID: "gpt-5.4", time: { created: 100 } } },
|
||||
@@ -512,13 +513,13 @@ describe("checkCompletionConditions continuation coverage", () => {
|
||||
const ctx = createMockContext(directory)
|
||||
ctx.sessionID = "ses_appended_child"
|
||||
setSessionAgent("ses_appended_child", "atlas")
|
||||
ctx.client.session.get = testCoerce<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
ctx.client.session.get = unsafeTestValue<RunContext["client"]["session"]["get"]>(mock(async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_appended_child" ? "ses_root_tracked" : undefined,
|
||||
},
|
||||
})))
|
||||
ctx.client.session.messages = testCoerce<RunContext["client"]["session"]["messages"]>(mock(async () => ({ data: [] })))
|
||||
ctx.client.session.messages = unsafeTestValue<RunContext["client"]["session"]["messages"]>(mock(async () => ({ data: [] })))
|
||||
|
||||
const { checkCompletionConditions } = await import("./completion")
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, mock, spyOn } from "bun:test"
|
||||
import type { RunContext, ChildSession, SessionStatus } from "./types"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const createMockContext = (overrides: {
|
||||
childrenBySession?: Record<string, ChildSession[]>
|
||||
@@ -13,7 +14,7 @@ const createMockContext = (overrides: {
|
||||
} = overrides
|
||||
|
||||
return {
|
||||
client: testCoerce<RunContext["client"]>({
|
||||
client: unsafeTestValue<RunContext["client"]>({
|
||||
session: {
|
||||
todo: mock(() => Promise.resolve({ data: [] })),
|
||||
children: mock((opts: { path: { id: string } }) =>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect, mock, spyOn } from "bun:test"
|
||||
import type { RunContext, Todo, ChildSession, SessionStatus } from "./types"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const createMockContext = (overrides: {
|
||||
todo?: Todo[]
|
||||
@@ -13,7 +14,7 @@ const createMockContext = (overrides: {
|
||||
} = overrides
|
||||
|
||||
return {
|
||||
client: testCoerce<RunContext["client"]>({
|
||||
client: unsafeTestValue<RunContext["client"]>({
|
||||
session: {
|
||||
todo: mock(() => Promise.resolve({ data: todo })),
|
||||
children: mock((opts: { path: { id: string } }) =>
|
||||
|
||||
@@ -2,6 +2,7 @@ const { describe, it, expect, spyOn } = require("bun:test")
|
||||
import type { RunContext } from "./types"
|
||||
import { createEventState } from "./events"
|
||||
import { handleSessionStatus, handleMessagePartUpdated, handleMessageUpdated, handleTuiToast } from "./event-handlers"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const createMockContext = (sessionID: string = "test-session"): RunContext => ({
|
||||
sessionID,
|
||||
@@ -23,7 +24,7 @@ describe("handleSessionStatus", () => {
|
||||
}
|
||||
|
||||
//#when - handleSessionStatus called with idle status
|
||||
handleSessionStatus(ctx, testCoerce(payload), state)
|
||||
handleSessionStatus(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then - state.mainSessionIdle === true
|
||||
expect(state.mainSessionIdle).toBe(true)
|
||||
@@ -44,7 +45,7 @@ describe("handleSessionStatus", () => {
|
||||
}
|
||||
|
||||
//#when - handleSessionStatus called with busy status
|
||||
handleSessionStatus(ctx, testCoerce(payload), state)
|
||||
handleSessionStatus(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then - state.mainSessionIdle === false
|
||||
expect(state.mainSessionIdle).toBe(false)
|
||||
@@ -65,7 +66,7 @@ describe("handleSessionStatus", () => {
|
||||
}
|
||||
|
||||
//#when - handleSessionStatus called with different session ID
|
||||
handleSessionStatus(ctx, testCoerce(payload), state)
|
||||
handleSessionStatus(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then - state.mainSessionIdle remains unchanged
|
||||
expect(state.mainSessionIdle).toBe(true)
|
||||
@@ -86,7 +87,7 @@ describe("handleSessionStatus", () => {
|
||||
}
|
||||
|
||||
//#when - handleSessionStatus called with camelCase sessionId
|
||||
handleSessionStatus(ctx, testCoerce(payload), state)
|
||||
handleSessionStatus(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then - state.mainSessionIdle === true
|
||||
expect(state.mainSessionIdle).toBe(true)
|
||||
@@ -114,7 +115,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, testCoerce(payload), state)
|
||||
handleMessagePartUpdated(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(true)
|
||||
@@ -142,7 +143,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, testCoerce(payload), state)
|
||||
handleMessagePartUpdated(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(false)
|
||||
@@ -170,7 +171,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, testCoerce(payload), state)
|
||||
handleMessagePartUpdated(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.currentTool).toBe("read")
|
||||
@@ -200,7 +201,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, testCoerce(payload), state)
|
||||
handleMessagePartUpdated(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.currentTool).toBeNull()
|
||||
@@ -225,7 +226,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleMessagePartUpdated(ctx, testCoerce(payload), state)
|
||||
handleMessagePartUpdated(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.hasReceivedMeaningfulWork).toBe(true)
|
||||
@@ -243,7 +244,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
|
||||
handleMessageUpdated(
|
||||
ctx,
|
||||
testCoerce({
|
||||
unsafeTestValue({
|
||||
type: "message.updated",
|
||||
properties: {
|
||||
info: {
|
||||
@@ -262,7 +263,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
// when
|
||||
handleMessagePartUpdated(
|
||||
ctx,
|
||||
testCoerce({
|
||||
unsafeTestValue({
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
@@ -280,7 +281,7 @@ describe("handleMessagePartUpdated", () => {
|
||||
|
||||
handleMessagePartUpdated(
|
||||
ctx,
|
||||
testCoerce({
|
||||
unsafeTestValue({
|
||||
type: "message.part.updated",
|
||||
properties: {
|
||||
part: {
|
||||
@@ -323,7 +324,7 @@ describe("handleTuiToast", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleTuiToast(ctx, testCoerce(payload), state)
|
||||
handleTuiToast(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.mainSessionError).toBe(true)
|
||||
@@ -344,7 +345,7 @@ describe("handleTuiToast", () => {
|
||||
}
|
||||
|
||||
//#when
|
||||
handleTuiToast(ctx, testCoerce(payload), state)
|
||||
handleTuiToast(ctx, unsafeTestValue(payload), state)
|
||||
|
||||
//#then
|
||||
expect(state.mainSessionError).toBe(false)
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as spawnWithWindowsHideModule from "../../shared/spawn-with-windows-hid
|
||||
import type { OpencodeClient } from "./types"
|
||||
import * as originalSdk from "@opencode-ai/sdk"
|
||||
import * as originalPortUtils from "../../shared/port-utils"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const mockServerClose = mock(() => {})
|
||||
const mockCreateOpencode = mock(() =>
|
||||
@@ -56,7 +57,7 @@ function createMockWriteStream(): MockWriteStream {
|
||||
|
||||
const createMockClient = (
|
||||
getResult?: { error?: unknown; data?: { id: string } }
|
||||
): OpencodeClient => (testCoerce<OpencodeClient>({
|
||||
): OpencodeClient => (unsafeTestValue<OpencodeClient>({
|
||||
session: {
|
||||
get: mock((opts: { path: { id: string } }) =>
|
||||
Promise.resolve(getResult ?? { data: { id: opts.path.id } })
|
||||
@@ -78,8 +79,8 @@ describe("integration: --json mode", () => {
|
||||
summary: "Test summary",
|
||||
}
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
|
||||
// when
|
||||
@@ -103,8 +104,8 @@ describe("integration: --json mode", () => {
|
||||
const mockStdout = createMockWriteStream()
|
||||
const mockStderr = createMockWriteStream()
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
manager.redirectToStderr()
|
||||
|
||||
@@ -272,8 +273,8 @@ describe("integration: option combinations", () => {
|
||||
summary: "Test completed",
|
||||
}
|
||||
const jsonManager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
jsonManager.redirectToStderr()
|
||||
spawnSpy.mockClear()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, it, expect, beforeEach } from "bun:test"
|
||||
import type { RunResult } from "./types"
|
||||
import { createJsonOutputManager } from "./json-output"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
interface MockWriteStream {
|
||||
write: (chunk: string) => boolean
|
||||
@@ -31,8 +32,8 @@ describe("createJsonOutputManager", () => {
|
||||
it("causes stdout writes to go to stderr", () => {
|
||||
// given
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
manager.redirectToStderr()
|
||||
|
||||
@@ -49,8 +50,8 @@ describe("createJsonOutputManager", () => {
|
||||
it("reverses the redirect", () => {
|
||||
// given
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
manager.redirectToStderr()
|
||||
|
||||
@@ -75,8 +76,8 @@ describe("createJsonOutputManager", () => {
|
||||
summary: "Test summary",
|
||||
}
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
|
||||
// when
|
||||
@@ -98,8 +99,8 @@ describe("createJsonOutputManager", () => {
|
||||
summary: "Test summary",
|
||||
}
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
|
||||
// when
|
||||
@@ -126,8 +127,8 @@ describe("createJsonOutputManager", () => {
|
||||
summary: "Test",
|
||||
}
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
manager.redirectToStderr()
|
||||
|
||||
@@ -148,8 +149,8 @@ describe("createJsonOutputManager", () => {
|
||||
it("work correctly", () => {
|
||||
// given
|
||||
const manager = createJsonOutputManager({
|
||||
stdout: testCoerce<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: testCoerce<NodeJS.WriteStream>(mockStderr),
|
||||
stdout: unsafeTestValue<NodeJS.WriteStream>(mockStdout),
|
||||
stderr: unsafeTestValue<NodeJS.WriteStream>(mockStderr),
|
||||
})
|
||||
|
||||
// when
|
||||
|
||||
@@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, it, expect, mock, spyOn } from "bun:te
|
||||
import type { RunContext, Todo, ChildSession, SessionStatus } from "./types"
|
||||
import { createEventState } from "./events"
|
||||
import { pollForCompletion } from "./poll-for-completion"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
const createMockContext = (overrides: {
|
||||
todo?: Todo[]
|
||||
@@ -15,7 +16,7 @@ const createMockContext = (overrides: {
|
||||
} = overrides
|
||||
|
||||
return {
|
||||
client: testCoerce<RunContext["client"]>({
|
||||
client: unsafeTestValue<RunContext["client"]>({
|
||||
session: {
|
||||
todo: mock(() => Promise.resolve({ data: todo })),
|
||||
children: mock((opts: { path: { id: string } }) =>
|
||||
@@ -124,7 +125,7 @@ describe("pollForCompletion", () => {
|
||||
let todoCallCount = 0
|
||||
let busyInserted = false
|
||||
|
||||
;(testCoerce(ctx.client.session)).todo = mock(async () => {
|
||||
;(unsafeTestValue(ctx.client.session)).todo = mock(async () => {
|
||||
todoCallCount++
|
||||
if (todoCallCount === 1 && !busyInserted) {
|
||||
busyInserted = true
|
||||
@@ -133,10 +134,10 @@ describe("pollForCompletion", () => {
|
||||
}
|
||||
return { data: [] }
|
||||
})
|
||||
;(testCoerce(ctx.client.session)).children = mock(() =>
|
||||
;(unsafeTestValue(ctx.client.session)).children = mock(() =>
|
||||
Promise.resolve({ data: [] })
|
||||
)
|
||||
;(testCoerce(ctx.client.session)).status = mock(() =>
|
||||
;(unsafeTestValue(ctx.client.session)).status = mock(() =>
|
||||
Promise.resolve({ data: {} })
|
||||
)
|
||||
|
||||
@@ -322,17 +323,17 @@ describe("pollForCompletion", () => {
|
||||
const abortController = new AbortController()
|
||||
let pollTick = 0
|
||||
|
||||
;(testCoerce(ctx.client.session)).todo = mock(async () => {
|
||||
;(unsafeTestValue(ctx.client.session)).todo = mock(async () => {
|
||||
pollTick++
|
||||
if (pollTick === 2) {
|
||||
eventState.currentTool = "task"
|
||||
}
|
||||
return { data: [] }
|
||||
})
|
||||
;(testCoerce(ctx.client.session)).children = mock(() =>
|
||||
;(unsafeTestValue(ctx.client.session)).children = mock(() =>
|
||||
Promise.resolve({ data: [] })
|
||||
)
|
||||
;(testCoerce(ctx.client.session)).status = mock(() =>
|
||||
;(unsafeTestValue(ctx.client.session)).status = mock(() =>
|
||||
Promise.resolve({ data: {} })
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="bun-types" />
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
import { beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
|
||||
import { resolveSession } from "./session-resolver";
|
||||
@@ -10,7 +11,7 @@ const createMockClient = (overrides: {
|
||||
} = {}): OpencodeClient => {
|
||||
const { getResult, createResults = [] } = overrides
|
||||
let createCallIndex = 0
|
||||
return testCoerce<OpencodeClient>({
|
||||
return unsafeTestValue<OpencodeClient>({
|
||||
session: {
|
||||
get: mock((opts: { path: { id: string } }) =>
|
||||
Promise.resolve(getResult ?? { data: { id: opts.path.id } })
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { createTimestampTransformer, createTimestampedStdoutController } from "./timestamp-output"
|
||||
import { unsafeTestValue } from "../../../test-support/unsafe-test-value"
|
||||
|
||||
function createLocalDate(hours: number, minutes: number, seconds: number): Date {
|
||||
return new Date(2026, 1, 19, hours, minutes, seconds)
|
||||
@@ -87,7 +88,7 @@ describe("createTimestampedStdoutController", () => {
|
||||
it("prefixes stdout writes when enabled", () => {
|
||||
// given
|
||||
const stdout = createMockWriteStream()
|
||||
const controller = createTimestampedStdoutController(testCoerce<NodeJS.WriteStream>(stdout))
|
||||
const controller = createTimestampedStdoutController(unsafeTestValue<NodeJS.WriteStream>(stdout))
|
||||
|
||||
// when
|
||||
controller.enable()
|
||||
@@ -101,7 +102,7 @@ describe("createTimestampedStdoutController", () => {
|
||||
it("restores original write function", () => {
|
||||
// given
|
||||
const stdout = createMockWriteStream()
|
||||
const controller = createTimestampedStdoutController(testCoerce<NodeJS.WriteStream>(stdout))
|
||||
const controller = createTimestampedStdoutController(unsafeTestValue<NodeJS.WriteStream>(stdout))
|
||||
controller.enable()
|
||||
|
||||
// when
|
||||
@@ -118,7 +119,7 @@ describe("createTimestampedStdoutController", () => {
|
||||
it("supports Uint8Array chunks and encoding", () => {
|
||||
// given
|
||||
const stdout = createMockWriteStream()
|
||||
const controller = createTimestampedStdoutController(testCoerce<NodeJS.WriteStream>(stdout))
|
||||
const controller = createTimestampedStdoutController(unsafeTestValue<NodeJS.WriteStream>(stdout))
|
||||
|
||||
// when
|
||||
controller.enable()
|
||||
|
||||
Reference in New Issue
Block a user