test(hooks): update multiple hook test suites
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -4,35 +4,31 @@ const replaceEmptyTextPartsAsync = mock(() => Promise.resolve(false))
|
|||||||
const injectTextPartAsync = mock(() => Promise.resolve(false))
|
const injectTextPartAsync = mock(() => Promise.resolve(false))
|
||||||
const findMessagesWithEmptyTextPartsFromSDK = mock(() => Promise.resolve([] as string[]))
|
const findMessagesWithEmptyTextPartsFromSDK = mock(() => Promise.resolve([] as string[]))
|
||||||
|
|
||||||
async function importFreshMessageBuilder(): Promise<typeof import("./message-builder")> {
|
mock.module("../../shared/logger", () => ({
|
||||||
mock.module("../../shared/logger", () => ({
|
log: () => {},
|
||||||
log: () => {},
|
}))
|
||||||
}))
|
|
||||||
|
|
||||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
mock.module("../../shared/opencode-storage-detection", () => ({
|
||||||
isSqliteBackend: () => true,
|
isSqliteBackend: () => true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const emptyTextMockFactory = () => ({
|
const emptyTextMockFactory = () => ({
|
||||||
findMessagesWithEmptyTextParts: () => [],
|
findMessagesWithEmptyTextParts: () => [],
|
||||||
replaceEmptyTextParts: () => false,
|
replaceEmptyTextParts: () => false,
|
||||||
replaceEmptyTextPartsAsync,
|
replaceEmptyTextPartsAsync,
|
||||||
findMessagesWithEmptyTextPartsFromSDK,
|
findMessagesWithEmptyTextPartsFromSDK,
|
||||||
})
|
})
|
||||||
mock.module("../session-recovery/storage/empty-text", emptyTextMockFactory)
|
mock.module("../session-recovery/storage/empty-text", emptyTextMockFactory)
|
||||||
mock.module("../session-recovery/storage/empty-text.ts", emptyTextMockFactory)
|
mock.module("../session-recovery/storage/empty-text.ts", emptyTextMockFactory)
|
||||||
|
|
||||||
const textPartInjectorMockFactory = () => ({
|
const textPartInjectorMockFactory = () => ({
|
||||||
injectTextPart: () => false,
|
injectTextPart: () => false,
|
||||||
injectTextPartAsync,
|
injectTextPartAsync,
|
||||||
})
|
})
|
||||||
mock.module("../session-recovery/storage/text-part-injector", textPartInjectorMockFactory)
|
mock.module("../session-recovery/storage/text-part-injector", textPartInjectorMockFactory)
|
||||||
mock.module("../session-recovery/storage/text-part-injector.ts", textPartInjectorMockFactory)
|
mock.module("../session-recovery/storage/text-part-injector.ts", textPartInjectorMockFactory)
|
||||||
|
|
||||||
const module = await import(`./message-builder?test=${Date.now()}-${Math.random()}`)
|
const messageBuilderModulePromise = import("./message-builder")
|
||||||
mock.restore()
|
|
||||||
return module
|
|
||||||
}
|
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
@@ -49,7 +45,7 @@ describe("sanitizeEmptyMessagesBeforeSummarize", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("#given sqlite message with tool content and empty text part #when sanitizing #then it fixes the mixed-content message", async () => {
|
test("#given sqlite message with tool content and empty text part #when sanitizing #then it fixes the mixed-content message", async () => {
|
||||||
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await importFreshMessageBuilder()
|
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await messageBuilderModulePromise
|
||||||
const client = {
|
const client = {
|
||||||
session: {
|
session: {
|
||||||
messages: mock(() => Promise.resolve({
|
messages: mock(() => Promise.resolve({
|
||||||
@@ -76,7 +72,7 @@ describe("sanitizeEmptyMessagesBeforeSummarize", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("#given sqlite message with mixed content and failed replacement #when sanitizing #then it injects the placeholder text part", async () => {
|
test("#given sqlite message with mixed content and failed replacement #when sanitizing #then it injects the placeholder text part", async () => {
|
||||||
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await importFreshMessageBuilder()
|
const { sanitizeEmptyMessagesBeforeSummarize, PLACEHOLDER_TEXT } = await messageBuilderModulePromise
|
||||||
const client = {
|
const client = {
|
||||||
session: {
|
session: {
|
||||||
messages: mock(() => Promise.resolve({
|
messages: mock(() => Promise.resolve({
|
||||||
|
|||||||
@@ -1,45 +1,52 @@
|
|||||||
import { describe, test, expect, mock, beforeEach, afterAll } from "bun:test"
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"
|
||||||
import { truncateUntilTargetTokens } from "./storage"
|
import type { ToolResultInfo } from "./tool-part-types"
|
||||||
import * as storage from "./storage"
|
|
||||||
|
|
||||||
// Mock the entire module
|
type TruncateToolResult = {
|
||||||
mock.module("./storage", () => {
|
success: boolean
|
||||||
return {
|
toolName?: string
|
||||||
...storage,
|
originalSize?: number
|
||||||
findToolResultsBySize: mock(() => []),
|
}
|
||||||
truncateToolResult: mock(() => ({ success: false })),
|
|
||||||
}
|
const findToolResultsBySize = mock<(_: string) => ToolResultInfo[]>(() => [])
|
||||||
})
|
const truncateToolResult = mock<(_: string) => TruncateToolResult>(() => ({ success: false }))
|
||||||
|
|
||||||
|
mock.module("./tool-result-storage", () => ({
|
||||||
|
findToolResultsBySize,
|
||||||
|
truncateToolResult,
|
||||||
|
}))
|
||||||
|
|
||||||
|
async function importFreshStorage(): Promise<typeof import("./storage")> {
|
||||||
|
return import(`./storage?test=${Date.now()}-${Math.random()}`)
|
||||||
|
}
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
mock.module("./storage", () => storage)
|
|
||||||
mock.restore()
|
mock.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("truncateUntilTargetTokens", () => {
|
describe("truncateUntilTargetTokens", () => {
|
||||||
const sessionID = "test-session"
|
const sessionID = "test-session"
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Reset mocks
|
|
||||||
const { findToolResultsBySize, truncateToolResult } = require("./storage")
|
|
||||||
findToolResultsBySize.mockReset()
|
findToolResultsBySize.mockReset()
|
||||||
truncateToolResult.mockReset()
|
truncateToolResult.mockReset()
|
||||||
|
findToolResultsBySize.mockReturnValue([])
|
||||||
|
truncateToolResult.mockReturnValue({ success: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("truncates only until target is reached", async () => {
|
test("truncates only until target is reached", async () => {
|
||||||
const { findToolResultsBySize, truncateToolResult } = require("./storage")
|
const { truncateUntilTargetTokens } = await importFreshStorage()
|
||||||
|
|
||||||
// given: Two tool results, each 1000 chars. Target reduction is 500 chars.
|
// given: Two tool results, each 1000 chars. Target reduction is 500 chars.
|
||||||
const results = [
|
const results = [
|
||||||
{ partPath: "path1", partId: "id1", messageID: "m1", toolName: "tool1", outputSize: 1000 },
|
{ partPath: "path1", partId: "id1", messageID: "m1", toolName: "tool1", outputSize: 1000 },
|
||||||
{ partPath: "path2", partId: "id2", messageID: "m2", toolName: "tool2", outputSize: 1000 },
|
{ partPath: "path2", partId: "id2", messageID: "m2", toolName: "tool2", outputSize: 1000 },
|
||||||
]
|
]
|
||||||
|
|
||||||
findToolResultsBySize.mockReturnValue(results)
|
findToolResultsBySize.mockReturnValue(results)
|
||||||
truncateToolResult.mockImplementation((path: string) => ({
|
truncateToolResult.mockImplementation((path: string) => ({
|
||||||
success: true,
|
success: true,
|
||||||
toolName: path === "path1" ? "tool1" : "tool2",
|
toolName: path === "path1" ? "tool1" : "tool2",
|
||||||
originalSize: 1000
|
originalSize: 1000,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// when: currentTokens=1000, maxTokens=1000, targetRatio=0.5 (target=500, reduce=500)
|
// when: currentTokens=1000, maxTokens=1000, targetRatio=0.5 (target=500, reduce=500)
|
||||||
@@ -55,19 +62,19 @@ describe("truncateUntilTargetTokens", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test("truncates all if target not reached", async () => {
|
test("truncates all if target not reached", async () => {
|
||||||
const { findToolResultsBySize, truncateToolResult } = require("./storage")
|
const { truncateUntilTargetTokens } = await importFreshStorage()
|
||||||
|
|
||||||
// given: Two tool results, each 100 chars. Target reduction is 500 chars.
|
// given: Two tool results, each 100 chars. Target reduction is 500 chars.
|
||||||
const results = [
|
const results = [
|
||||||
{ partPath: "path1", partId: "id1", messageID: "m1", toolName: "tool1", outputSize: 100 },
|
{ partPath: "path1", partId: "id1", messageID: "m1", toolName: "tool1", outputSize: 100 },
|
||||||
{ partPath: "path2", partId: "id2", messageID: "m2", toolName: "tool2", outputSize: 100 },
|
{ partPath: "path2", partId: "id2", messageID: "m2", toolName: "tool2", outputSize: 100 },
|
||||||
]
|
]
|
||||||
|
|
||||||
findToolResultsBySize.mockReturnValue(results)
|
findToolResultsBySize.mockReturnValue(results)
|
||||||
truncateToolResult.mockImplementation((path: string) => ({
|
truncateToolResult.mockImplementation((path: string) => ({
|
||||||
success: true,
|
success: true,
|
||||||
toolName: path === "path1" ? "tool1" : "tool2",
|
toolName: path === "path1" ? "tool1" : "tool2",
|
||||||
originalSize: 100
|
originalSize: 100,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// when: reduce 500 chars
|
// when: reduce 500 chars
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { randomUUID } from "node:crypto"
|
|||||||
import type { PluginInput } from "@opencode-ai/plugin"
|
import type { PluginInput } from "@opencode-ai/plugin"
|
||||||
import { createAtlasHook } from "./atlas-hook"
|
import { createAtlasHook } from "./atlas-hook"
|
||||||
import { clearBoulderState, writeBoulderState } from "../../features/boulder-state"
|
import { clearBoulderState, writeBoulderState } from "../../features/boulder-state"
|
||||||
import { _resetForTesting, registerAgentName, setSessionAgent } from "../../features/claude-code-session-state"
|
import { _resetForTesting, clearSessionAgent, registerAgentName, setSessionAgent } from "../../features/claude-code-session-state"
|
||||||
|
|
||||||
// Force process isolation in CI runner (globalThis.setTimeout override conflicts with other atlas tests)
|
// Force process isolation in CI runner (globalThis.setTimeout override conflicts with other atlas tests)
|
||||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
mock.module("../../shared/opencode-storage-detection", () => ({
|
||||||
@@ -401,7 +401,8 @@ describe("atlas background task retry", () => {
|
|||||||
// when
|
// when
|
||||||
await hook.handler({ event: { type: "session.idle", properties: { sessionID: descendantSessionID } } })
|
await hook.handler({ event: { type: "session.idle", properties: { sessionID: descendantSessionID } } })
|
||||||
expect(capturedTimers.size).toBe(1)
|
expect(capturedTimers.size).toBe(1)
|
||||||
descendantAgent = "sisyphus-junior"
|
descendantAgent = "prometheus"
|
||||||
|
clearSessionAgent(descendantSessionID)
|
||||||
backgroundRunning = false
|
backgroundRunning = false
|
||||||
await firePendingTimers()
|
await firePendingTimers()
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,6 @@ const testDirs: string[] = []
|
|||||||
const TEST_STORAGE_ROOT = join(tmpdir(), `atlas-session-last-agent-${Date.now()}`)
|
const TEST_STORAGE_ROOT = join(tmpdir(), `atlas-session-last-agent-${Date.now()}`)
|
||||||
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
|
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
|
||||||
|
|
||||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
|
||||||
isSqliteBackend: () => false,
|
|
||||||
}))
|
|
||||||
|
|
||||||
mock.module("../../shared/opencode-message-dir", () => ({
|
|
||||||
getMessageDir: (sessionID: string) => {
|
|
||||||
const directPath = join(TEST_MESSAGE_STORAGE, sessionID)
|
|
||||||
return require("node:fs").existsSync(directPath) ? directPath : null
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
afterAll(() => { mock.restore() })
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
while (testDirs.length > 0) {
|
while (testDirs.length > 0) {
|
||||||
const directory = testDirs.pop()
|
const directory = testDirs.pop()
|
||||||
@@ -31,11 +18,17 @@ afterEach(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function importFreshSessionLastAgentModule(): Promise<typeof import("./session-last-agent")> {
|
||||||
|
return import(`./session-last-agent?test=${Date.now()}-${Math.random()}`)
|
||||||
|
}
|
||||||
|
|
||||||
function createTempMessageDir(sessionID: string): string {
|
function createTempMessageDir(sessionID: string): string {
|
||||||
const directory = mkdtempSync(join(tmpdir(), "atlas-session-last-agent-json-"))
|
const directory = mkdtempSync(join(tmpdir(), "atlas-session-last-agent-json-"))
|
||||||
testDirs.push(directory)
|
testDirs.push(directory)
|
||||||
const messageDir = join(TEST_MESSAGE_STORAGE, sessionID)
|
const messageDir = join(TEST_MESSAGE_STORAGE, sessionID)
|
||||||
|
rmSync(messageDir, { recursive: true, force: true })
|
||||||
mkdirSync(messageDir, { recursive: true })
|
mkdirSync(messageDir, { recursive: true })
|
||||||
|
testDirs.push(messageDir)
|
||||||
return messageDir
|
return messageDir
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,10 +50,20 @@ describe("getLastAgentFromSession JSON backend", () => {
|
|||||||
time: { created: 50 },
|
time: { created: 50 },
|
||||||
}), "utf-8")
|
}), "utf-8")
|
||||||
|
|
||||||
const { getLastAgentFromSession } = await import("./session-last-agent")
|
const { getLastAgentFromSession } = await importFreshSessionLastAgentModule()
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession(sessionID)
|
const result = await getLastAgentFromSession(sessionID, undefined, {
|
||||||
|
isSqliteBackend: () => false,
|
||||||
|
getMessageDir: (targetSessionID: string) => {
|
||||||
|
const directPath = join(TEST_MESSAGE_STORAGE, targetSessionID)
|
||||||
|
return require("node:fs").existsSync(directPath) ? directPath : null
|
||||||
|
},
|
||||||
|
isCompactionMessage: (message: { agent?: unknown }) => {
|
||||||
|
return typeof message.agent === "string" && message.agent.toLowerCase() === "compaction"
|
||||||
|
},
|
||||||
|
hasCompactionPartInStorage: () => false,
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("atlas")
|
expect(result).toBe("atlas")
|
||||||
@@ -71,6 +74,7 @@ describe("getLastAgentFromSession JSON backend", () => {
|
|||||||
const sessionID = "ses_json_compaction_marker"
|
const sessionID = "ses_json_compaction_marker"
|
||||||
const messageDir = createTempMessageDir(sessionID)
|
const messageDir = createTempMessageDir(sessionID)
|
||||||
const compactionMessageID = "msg_test_atlas_compaction_marker"
|
const compactionMessageID = "msg_test_atlas_compaction_marker"
|
||||||
|
const regularMessageID = `msg_${sessionID}_regular`
|
||||||
const partDir = join(PART_STORAGE, compactionMessageID)
|
const partDir = join(PART_STORAGE, compactionMessageID)
|
||||||
testDirs.push(partDir)
|
testDirs.push(partDir)
|
||||||
writeFileSync(join(messageDir, "msg_0001.json"), JSON.stringify({
|
writeFileSync(join(messageDir, "msg_0001.json"), JSON.stringify({
|
||||||
@@ -84,15 +88,27 @@ describe("getLastAgentFromSession JSON backend", () => {
|
|||||||
}), "utf-8")
|
}), "utf-8")
|
||||||
|
|
||||||
writeFileSync(join(messageDir, "msg_0002.json"), JSON.stringify({
|
writeFileSync(join(messageDir, "msg_0002.json"), JSON.stringify({
|
||||||
id: "msg_0002",
|
id: regularMessageID,
|
||||||
agent: "sisyphus-junior",
|
agent: "sisyphus-junior",
|
||||||
time: { created: 100 },
|
time: { created: 100 },
|
||||||
}), "utf-8")
|
}), "utf-8")
|
||||||
|
|
||||||
const { getLastAgentFromSession } = await import("./session-last-agent")
|
const { getLastAgentFromSession } = await importFreshSessionLastAgentModule()
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession(sessionID)
|
const result = await getLastAgentFromSession(sessionID, undefined, {
|
||||||
|
isSqliteBackend: () => false,
|
||||||
|
getMessageDir: (targetSessionID: string) => {
|
||||||
|
const directPath = join(TEST_MESSAGE_STORAGE, targetSessionID)
|
||||||
|
return require("node:fs").existsSync(directPath) ? directPath : null
|
||||||
|
},
|
||||||
|
isCompactionMessage: (message: { agent?: unknown }) => {
|
||||||
|
return typeof message.agent === "string" && message.agent.toLowerCase() === "compaction"
|
||||||
|
},
|
||||||
|
hasCompactionPartInStorage: (messageID: string | undefined) => {
|
||||||
|
return messageID === compactionMessageID
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("sisyphus-junior")
|
expect(result).toBe("sisyphus-junior")
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
export {}
|
export {}
|
||||||
const { describe, expect, mock, test, afterAll } = require("bun:test")
|
const { describe, expect, test } = require("bun:test")
|
||||||
|
|
||||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
|
||||||
isSqliteBackend: () => true,
|
|
||||||
}))
|
|
||||||
|
|
||||||
afterAll(() => { mock.restore() })
|
|
||||||
|
|
||||||
const { getLastAgentFromSession } = await import("./session-last-agent")
|
const { getLastAgentFromSession } = await import("./session-last-agent")
|
||||||
|
|
||||||
@@ -25,7 +19,9 @@ describe("getLastAgentFromSession SQLite backend ordering", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession("ses_sqlite_last_agent", client as never)
|
const result = await getLastAgentFromSession("ses_sqlite_last_agent", client as never, {
|
||||||
|
isSqliteBackend: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("sisyphus-junior")
|
expect(result).toBe("sisyphus-junior")
|
||||||
@@ -46,7 +42,9 @@ describe("getLastAgentFromSession SQLite backend ordering", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession("ses_sqlite_last_agent_equal_time", client as never)
|
const result = await getLastAgentFromSession("ses_sqlite_last_agent_equal_time", client as never, {
|
||||||
|
isSqliteBackend: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("sisyphus-junior")
|
expect(result).toBe("sisyphus-junior")
|
||||||
@@ -70,7 +68,9 @@ describe("getLastAgentFromSession SQLite backend ordering", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession("ses_sqlite_compaction_marker", client as never)
|
const result = await getLastAgentFromSession("ses_sqlite_compaction_marker", client as never, {
|
||||||
|
isSqliteBackend: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("sisyphus")
|
expect(result).toBe("sisyphus")
|
||||||
@@ -87,7 +87,9 @@ describe("getLastAgentFromSession SQLite backend ordering", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = await getLastAgentFromSession("ses_sqlite_error", client as never)
|
const result = await getLastAgentFromSession("ses_sqlite_error", client as never, {
|
||||||
|
isSqliteBackend: () => true,
|
||||||
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBeNull()
|
expect(result).toBeNull()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, expect, it, beforeEach, afterEach, spyOn } from "bun:test"
|
import { describe, expect, it, beforeEach, afterEach, spyOn, mock } from "bun:test"
|
||||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||||
import { tmpdir } from "node:os"
|
import { tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
@@ -13,12 +13,7 @@ import type {
|
|||||||
// Import real shared module to avoid mock leaking to other test files
|
// Import real shared module to avoid mock leaking to other test files
|
||||||
import * as shared from "../../shared"
|
import * as shared from "../../shared"
|
||||||
|
|
||||||
// Spy on log instead of mocking the entire module
|
type AutoSlashCommandModule = typeof import("./hook")
|
||||||
const logMock = spyOn(shared, "log").mockImplementation(() => {})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const { createAutoSlashCommandHook } = await import("./index")
|
|
||||||
|
|
||||||
function createMockInput(sessionID: string, messageID?: string): AutoSlashCommandHookInput {
|
function createMockInput(sessionID: string, messageID?: string): AutoSlashCommandHookInput {
|
||||||
return {
|
return {
|
||||||
@@ -44,16 +39,26 @@ function createMockOutput(text: string): AutoSlashCommandHookOutput {
|
|||||||
describe("createAutoSlashCommandHook", () => {
|
describe("createAutoSlashCommandHook", () => {
|
||||||
let tempDir = ""
|
let tempDir = ""
|
||||||
let originalWorkingDirectory = ""
|
let originalWorkingDirectory = ""
|
||||||
|
let logCalls: Array<[string, unknown?]>
|
||||||
|
let createAutoSlashCommandHook: AutoSlashCommandModule["createAutoSlashCommandHook"]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
logMock.mockClear()
|
mock.restore()
|
||||||
|
logCalls = []
|
||||||
|
spyOn(shared, "log").mockImplementation((message: string, data?: unknown) => {
|
||||||
|
logCalls.push([message, data])
|
||||||
|
})
|
||||||
tempDir = mkdtempSync(join(tmpdir(), "omo-auto-slash-hook-test-"))
|
tempDir = mkdtempSync(join(tmpdir(), "omo-auto-slash-hook-test-"))
|
||||||
originalWorkingDirectory = process.cwd()
|
originalWorkingDirectory = process.cwd()
|
||||||
|
|
||||||
|
const autoSlashCommandModule = await import(`./hook?test=${Date.now()}-${Math.random()}`)
|
||||||
|
createAutoSlashCommandHook = autoSlashCommandModule.createAutoSlashCommandHook
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
process.chdir(originalWorkingDirectory)
|
process.chdir(originalWorkingDirectory)
|
||||||
rmSync(tempDir, { recursive: true, force: true })
|
rmSync(tempDir, { recursive: true, force: true })
|
||||||
|
mock.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("slash command replacement", () => {
|
describe("slash command replacement", () => {
|
||||||
@@ -237,7 +242,7 @@ describe("createAutoSlashCommandHook", () => {
|
|||||||
|
|
||||||
// when hook is called
|
// when hook is called
|
||||||
// then should not throw
|
// then should not throw
|
||||||
await expect(hook["chat.message"](input, output)).resolves.toBeUndefined()
|
await hook["chat.message"](input, output)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle just slash", async () => {
|
it("should handle just slash", async () => {
|
||||||
@@ -374,13 +379,13 @@ describe("createAutoSlashCommandHook", () => {
|
|||||||
await hook["command.execute.before"](input, output)
|
await hook["command.execute.before"](input, output)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(logMock).toHaveBeenCalledWith(
|
expect(logCalls).toContainEqual([
|
||||||
"[auto-slash-command] command.execute.before received",
|
"[auto-slash-command] command.execute.before received",
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
command: "some-command",
|
command: "some-command",
|
||||||
arguments: "arg1 arg2 arg3",
|
arguments: "arg1 arg2 arg3",
|
||||||
})
|
}),
|
||||||
)
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import type {
|
|||||||
CommandExecuteBeforeOutput,
|
CommandExecuteBeforeOutput,
|
||||||
} from "../types"
|
} from "../types"
|
||||||
import * as shared from "../../../shared"
|
import * as shared from "../../../shared"
|
||||||
|
import * as executorModule from "../executor"
|
||||||
|
|
||||||
|
type AutoSlashCommandModule = typeof import("../hook")
|
||||||
|
|
||||||
const executeSlashCommandMock = mock(
|
const executeSlashCommandMock = mock(
|
||||||
async (parsed: { command: string; args: string; raw: string }) => ({
|
async (parsed: { command: string; args: string; raw: string }) => ({
|
||||||
@@ -15,21 +18,11 @@ const executeSlashCommandMock = mock(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
mock.module("../executor", () => ({
|
|
||||||
executeSlashCommand: executeSlashCommandMock,
|
|
||||||
}))
|
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
mock.restore()
|
mock.restore()
|
||||||
// Restore the real executor module so subsequent test files in the same batch
|
|
||||||
// (e.g. executor-resolution.test.ts) don't get the mocked version
|
|
||||||
const realExecutor = await import("../executor")
|
|
||||||
mock.module("../executor", () => realExecutor)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const logMock = spyOn(shared, "log").mockImplementation(() => {})
|
let createAutoSlashCommandHook: AutoSlashCommandModule["createAutoSlashCommandHook"]
|
||||||
|
|
||||||
const { createAutoSlashCommandHook } = await import("../hook")
|
|
||||||
|
|
||||||
function createChatInput(sessionID: string, messageID: string): AutoSlashCommandHookInput {
|
function createChatInput(sessionID: string, messageID: string): AutoSlashCommandHookInput {
|
||||||
return {
|
return {
|
||||||
@@ -60,9 +53,14 @@ function createCommandOutput(text: string): CommandExecuteBeforeOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("createAutoSlashCommandHook leak prevention", () => {
|
describe("createAutoSlashCommandHook leak prevention", () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
mock.restore()
|
||||||
executeSlashCommandMock.mockClear()
|
executeSlashCommandMock.mockClear()
|
||||||
logMock.mockClear()
|
spyOn(executorModule, "executeSlashCommand").mockImplementation(executeSlashCommandMock)
|
||||||
|
spyOn(shared, "log").mockImplementation(() => {})
|
||||||
|
|
||||||
|
const autoSlashCommandModule = await import(`../hook?test=${Date.now()}-${Math.random()}`)
|
||||||
|
createAutoSlashCommandHook = autoSlashCommandModule.createAutoSlashCommandHook
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#given hook with sessionProcessedCommandExecutions", () => {
|
describe("#given hook with sessionProcessedCommandExecutions", () => {
|
||||||
|
|||||||
@@ -146,8 +146,8 @@ describe("keyword-detector session filtering", () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// then - search keyword should be filtered out based on mainSessionID comparison
|
// then - search keyword should be filtered out based on mainSessionID comparison
|
||||||
const skipLog = logCalls.find(c => c.msg.includes("Skipping non-ultrawork keywords in non-main session"))
|
expect(output.message.variant).toBeUndefined()
|
||||||
expect(skipLog).toBeDefined()
|
expect(output.parts[0]?.text).toBe("search mode 찾아줘")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("should allow ultrawork keywords in non-main session", async () => {
|
test("should allow ultrawork keywords in non-main session", async () => {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const mockMigrateLegacyPluginEntry = mock(() => true)
|
|||||||
mock.module("./plugin-entry-migrator", () => ({
|
mock.module("./plugin-entry-migrator", () => ({
|
||||||
migrateLegacyPluginEntry: mockMigrateLegacyPluginEntry,
|
migrateLegacyPluginEntry: mockMigrateLegacyPluginEntry,
|
||||||
}))
|
}))
|
||||||
|
mock.module("./plugin-entry-migrator.ts", () => ({
|
||||||
|
migrateLegacyPluginEntry: mockMigrateLegacyPluginEntry,
|
||||||
|
}))
|
||||||
|
|
||||||
async function importFreshAutoMigrateModule(): Promise<typeof import("./auto-migrate")> {
|
const autoMigrateModulePromise = import("./auto-migrate")
|
||||||
return import(`./auto-migrate?test=${Date.now()}-${Math.random()}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("autoMigrateLegacyPluginEntry", () => {
|
describe("autoMigrateLegacyPluginEntry", () => {
|
||||||
let testConfigDir = ""
|
let testConfigDir = ""
|
||||||
@@ -35,7 +36,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2) + "\n",
|
JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2) + "\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -56,7 +57,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2) + "\n",
|
JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2) + "\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -77,7 +78,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
JSON.stringify({ plugin: ["oh-my-openagent", "oh-my-opencode"] }, null, 2) + "\n",
|
JSON.stringify({ plugin: ["oh-my-openagent", "oh-my-opencode"] }, null, 2) + "\n",
|
||||||
)
|
)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -92,7 +93,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
describe("#given no config file exists", () => {
|
describe("#given no config file exists", () => {
|
||||||
it("#then returns migrated false", async () => {
|
it("#then returns migrated false", async () => {
|
||||||
// given - empty dir
|
// given - empty dir
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -112,7 +113,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
'{\n // my config\n "plugin": ["oh-my-opencode"]\n}\n',
|
'{\n // my config\n "plugin": ["oh-my-opencode"]\n}\n',
|
||||||
)
|
)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -138,7 +139,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
@@ -157,7 +158,7 @@ describe("autoMigrateLegacyPluginEntry", () => {
|
|||||||
const original = JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2) + "\n"
|
const original = JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2) + "\n"
|
||||||
writeFileSync(join(testConfigDir, "opencode.json"), original)
|
writeFileSync(join(testConfigDir, "opencode.json"), original)
|
||||||
|
|
||||||
const { autoMigrateLegacyPluginEntry } = await importFreshAutoMigrateModule()
|
const { autoMigrateLegacyPluginEntry } = await autoMigrateModulePromise
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
const result = autoMigrateLegacyPluginEntry(testConfigDir)
|
||||||
|
|||||||
@@ -1,26 +1,37 @@
|
|||||||
import { describe, expect, test, beforeEach, afterEach, spyOn } from "bun:test"
|
import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test"
|
||||||
import { createRuntimeFallbackHook } from "./index"
|
|
||||||
import type { RuntimeFallbackConfig, OhMyOpenCodeConfig } from "../../config"
|
import type { RuntimeFallbackConfig, OhMyOpenCodeConfig } from "../../config"
|
||||||
import * as sharedModule from "../../shared"
|
import * as loggerModule from "../../shared/logger"
|
||||||
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
import { SessionCategoryRegistry } from "../../shared/session-category-registry"
|
||||||
|
|
||||||
|
type RuntimeFallbackModule = typeof import("./hook")
|
||||||
|
|
||||||
describe("runtime-fallback", () => {
|
describe("runtime-fallback", () => {
|
||||||
let logCalls: Array<{ msg: string; data?: unknown }>
|
let logCalls: Array<{ msg: string; data?: unknown }>
|
||||||
let logSpy: ReturnType<typeof spyOn>
|
|
||||||
let toastCalls: Array<{ title: string; message: string; variant: string }>
|
let toastCalls: Array<{ title: string; message: string; variant: string }>
|
||||||
|
let createRuntimeFallbackHook: RuntimeFallbackModule["createRuntimeFallbackHook"]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
mock.restore()
|
||||||
logCalls = []
|
logCalls = []
|
||||||
toastCalls = []
|
toastCalls = []
|
||||||
SessionCategoryRegistry.clear()
|
SessionCategoryRegistry.clear()
|
||||||
logSpy = spyOn(sharedModule, "log").mockImplementation((msg: string, data?: unknown) => {
|
|
||||||
logCalls.push({ msg, data })
|
const cacheBuster = `${Date.now()}-${Math.random()}`
|
||||||
})
|
|
||||||
|
mock.module("../../shared/logger", () => ({
|
||||||
|
...loggerModule,
|
||||||
|
log: (msg: string, data?: unknown) => {
|
||||||
|
logCalls.push({ msg, data })
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
const runtimeFallbackModule: RuntimeFallbackModule = await import(`./hook?test=${cacheBuster}`)
|
||||||
|
createRuntimeFallbackHook = runtimeFallbackModule.createRuntimeFallbackHook
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
SessionCategoryRegistry.clear()
|
SessionCategoryRegistry.clear()
|
||||||
logSpy?.mockRestore()
|
mock.restore()
|
||||||
})
|
})
|
||||||
|
|
||||||
function createMockPluginInput(overrides?: {
|
function createMockPluginInput(overrides?: {
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
import { existsSync, readFileSync, rmSync } from "node:fs"
|
import { existsSync, readFileSync, rmSync } from "node:fs"
|
||||||
|
import { randomUUID } from "node:crypto"
|
||||||
|
import { tmpdir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
import { describe, expect, it, mock } from "bun:test"
|
||||||
import { detectErrorType } from "./index"
|
import { detectErrorType } from "./index"
|
||||||
import { prependThinkingPart, prependThinkingPartAsync } from "./storage/thinking-prepend"
|
|
||||||
import { PART_STORAGE } from "../../shared/opencode-storage-paths"
|
|
||||||
|
|
||||||
const { describe, expect, it, mock } = require("bun:test")
|
const TEST_STORAGE_ROOT = join(tmpdir(), `session-recovery-thinking-prepend-${randomUUID()}`)
|
||||||
|
const TEST_PART_STORAGE = join(TEST_STORAGE_ROOT, "part")
|
||||||
|
|
||||||
|
mock.module("../../shared", () => ({
|
||||||
|
OPENCODE_STORAGE: TEST_STORAGE_ROOT,
|
||||||
|
MESSAGE_STORAGE: join(TEST_STORAGE_ROOT, "message"),
|
||||||
|
PART_STORAGE: TEST_PART_STORAGE,
|
||||||
|
log: () => {},
|
||||||
|
isSqliteBackend: () => false,
|
||||||
|
patchPart: async () => true,
|
||||||
|
normalizeSDKResponse: <TData>(response: { data?: TData }, fallback: TData) => response.data ?? fallback,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { prependThinkingPart, prependThinkingPartAsync } = await import("./storage/thinking-prepend")
|
||||||
|
|
||||||
describe("detectErrorType", () => {
|
describe("detectErrorType", () => {
|
||||||
describe("thinking_block_order errors", () => {
|
describe("thinking_block_order errors", () => {
|
||||||
@@ -295,7 +309,7 @@ type StoredPartRecord = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupParts(messageID: string): void {
|
function cleanupParts(messageID: string): void {
|
||||||
rmSync(join(PART_STORAGE, messageID), { recursive: true, force: true })
|
rmSync(join(TEST_PART_STORAGE, messageID), { recursive: true, force: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("thinking-prepend", () => {
|
describe("thinking-prepend", () => {
|
||||||
@@ -322,7 +336,7 @@ describe("thinking-prepend", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
const writtenPath = join(PART_STORAGE, targetMessageID, `${originalPart.id}.json`)
|
const writtenPath = join(TEST_PART_STORAGE, targetMessageID, `${originalPart.id}.json`)
|
||||||
expect(existsSync(writtenPath)).toBe(true)
|
expect(existsSync(writtenPath)).toBe(true)
|
||||||
expect(JSON.parse(readFileSync(writtenPath, "utf-8"))).toEqual(originalPart)
|
expect(JSON.parse(readFileSync(writtenPath, "utf-8"))).toEqual(originalPart)
|
||||||
|
|
||||||
@@ -344,7 +358,7 @@ describe("thinking-prepend", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
expect(existsSync(join(PART_STORAGE, targetMessageID))).toBe(false)
|
expect(existsSync(join(TEST_PART_STORAGE, targetMessageID))).toBe(false)
|
||||||
|
|
||||||
cleanupParts(targetMessageID)
|
cleanupParts(targetMessageID)
|
||||||
})
|
})
|
||||||
@@ -386,7 +400,7 @@ describe("thinking-prepend", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
expect(existsSync(join(PART_STORAGE, targetMessageID))).toBe(false)
|
expect(existsSync(join(TEST_PART_STORAGE, targetMessageID))).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("patches the original signed thinking part verbatim for sdk-backed recovery", async () => {
|
it("patches the original signed thinking part verbatim for sdk-backed recovery", async () => {
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
const { describe, it, expect, mock, beforeEach, afterEach, spyOn } = require("bun:test")
|
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
|
||||||
|
|
||||||
import type { MessageData } from "./types"
|
import type { MessageData } from "./types"
|
||||||
import * as storageDetection from "../../shared/opencode-storage-detection"
|
|
||||||
import * as storage from "./storage"
|
|
||||||
import { recoverToolResultMissing } from "./recover-tool-result-missing"
|
|
||||||
|
|
||||||
let sqliteBackend = false
|
let sqliteBackend = false
|
||||||
let storedParts: Array<{ type: string; id?: string; callID?: string; [key: string]: unknown }> = []
|
let storedParts: Array<{ type: string; id?: string; callID?: string; [key: string]: unknown }> = []
|
||||||
|
|
||||||
|
mock.module("../../shared/opencode-storage-detection", () => ({
|
||||||
|
isSqliteBackend: () => sqliteBackend,
|
||||||
|
}))
|
||||||
|
|
||||||
|
mock.module("./storage", () => ({
|
||||||
|
readParts: () => storedParts,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { recoverToolResultMissing } = await import("./recover-tool-result-missing")
|
||||||
|
|
||||||
const failedAssistantMsg: MessageData = {
|
const failedAssistantMsg: MessageData = {
|
||||||
info: { id: "msg_failed", role: "assistant" },
|
info: { id: "msg_failed", role: "assistant" },
|
||||||
parts: [],
|
parts: [],
|
||||||
@@ -31,9 +38,6 @@ describe("recoverToolResultMissing", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sqliteBackend = false
|
sqliteBackend = false
|
||||||
storedParts = []
|
storedParts = []
|
||||||
|
|
||||||
spyOn(storageDetection, "isSqliteBackend").mockImplementation(() => sqliteBackend)
|
|
||||||
spyOn(storage, "readParts").mockImplementation(() => storedParts)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
|
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test"
|
||||||
|
import { createAutoUpdateCheckerHook } from "../auto-update-checker/hook"
|
||||||
|
|
||||||
const mockShowConfigErrorsIfAny = mock(async () => {})
|
const mockShowConfigErrorsIfAny = mock(async () => {})
|
||||||
const mockShowModelCacheWarningIfNeeded = mock(async () => {})
|
const mockShowModelCacheWarningIfNeeded = mock(async () => {})
|
||||||
@@ -10,60 +11,6 @@ const mockRunBackgroundUpdateCheck = mock(async () => {})
|
|||||||
const mockGetCachedVersion = mock(() => "3.6.0")
|
const mockGetCachedVersion = mock(() => "3.6.0")
|
||||||
const mockGetLocalDevVersion = mock<(directory: string) => string | null>(() => null)
|
const mockGetLocalDevVersion = mock<(directory: string) => string | null>(() => null)
|
||||||
|
|
||||||
const _realConfigErrorsToast = require("../auto-update-checker/hook/config-errors-toast")
|
|
||||||
const _realModelCacheWarning = require("../auto-update-checker/hook/model-cache-warning")
|
|
||||||
const _realConnectedProvidersStatus = require("../auto-update-checker/hook/connected-providers-status")
|
|
||||||
const _realModelCapabilitiesStatus = require("../auto-update-checker/hook/model-capabilities-status")
|
|
||||||
const _realStartupToasts = require("../auto-update-checker/hook/startup-toasts")
|
|
||||||
const _realBackgroundUpdateCheck = require("../auto-update-checker/hook/background-update-check")
|
|
||||||
const _realChecker = require("../auto-update-checker/checker")
|
|
||||||
const _realLogger = require("../../shared/logger")
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
mock.module("../auto-update-checker/hook/config-errors-toast", () => _realConfigErrorsToast)
|
|
||||||
mock.module("../auto-update-checker/hook/model-cache-warning", () => _realModelCacheWarning)
|
|
||||||
mock.module("../auto-update-checker/hook/connected-providers-status", () => _realConnectedProvidersStatus)
|
|
||||||
mock.module("../auto-update-checker/hook/model-capabilities-status", () => _realModelCapabilitiesStatus)
|
|
||||||
mock.module("../auto-update-checker/hook/startup-toasts", () => _realStartupToasts)
|
|
||||||
mock.module("../auto-update-checker/hook/background-update-check", () => _realBackgroundUpdateCheck)
|
|
||||||
mock.module("../auto-update-checker/checker", () => _realChecker)
|
|
||||||
mock.module("../../shared/logger", () => _realLogger)
|
|
||||||
mock.restore()
|
|
||||||
})
|
|
||||||
|
|
||||||
type HookFactory = typeof import("../auto-update-checker/hook").createAutoUpdateCheckerHook
|
|
||||||
|
|
||||||
async function importFreshHookFactory(): Promise<HookFactory> {
|
|
||||||
mock.module("../auto-update-checker/hook/config-errors-toast", () => ({
|
|
||||||
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/hook/model-cache-warning", () => ({
|
|
||||||
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/hook/connected-providers-status", () => ({
|
|
||||||
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/hook/model-capabilities-status", () => ({
|
|
||||||
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/hook/startup-toasts", () => ({
|
|
||||||
showLocalDevToast: mockShowLocalDevToast,
|
|
||||||
showVersionToast: mockShowVersionToast,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/hook/background-update-check", () => ({
|
|
||||||
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
|
||||||
}))
|
|
||||||
mock.module("../auto-update-checker/checker", () => ({
|
|
||||||
getCachedVersion: mockGetCachedVersion,
|
|
||||||
getLocalDevVersion: mockGetLocalDevVersion,
|
|
||||||
}))
|
|
||||||
mock.module("../../shared/logger", () => ({
|
|
||||||
log: () => {},
|
|
||||||
}))
|
|
||||||
const hookModule = await import(`../auto-update-checker/hook?test-${Date.now()}-${Math.random()}`)
|
|
||||||
return hookModule.createAutoUpdateCheckerHook
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPluginInput() {
|
function createPluginInput() {
|
||||||
return {
|
return {
|
||||||
directory: "/test",
|
directory: "/test",
|
||||||
@@ -80,7 +27,7 @@ async function flushScheduledWork(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function runSessionCreatedEvent(
|
function runSessionCreatedEvent(
|
||||||
hook: ReturnType<HookFactory>,
|
hook: ReturnType<typeof createAutoUpdateCheckerHook>,
|
||||||
properties?: { info?: { parentID?: string } }
|
properties?: { info?: { parentID?: string } }
|
||||||
): void {
|
): void {
|
||||||
hook.event({
|
hook.event({
|
||||||
@@ -114,12 +61,22 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
it("skips startup toasts and checks in CLI run mode", async () => {
|
it("skips startup toasts and checks in CLI run mode", async () => {
|
||||||
//#given - CLI run mode enabled
|
//#given - CLI run mode enabled
|
||||||
process.env.OPENCODE_CLI_RUN_MODE = "true"
|
process.env.OPENCODE_CLI_RUN_MODE = "true"
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
|
||||||
|
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput(), {
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {
|
||||||
showStartupToast: true,
|
showStartupToast: true,
|
||||||
isSisyphusEnabled: true,
|
isSisyphusEnabled: true,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
|
}, {
|
||||||
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when - session.created event arrives
|
//#when - session.created event arrives
|
||||||
@@ -138,8 +95,18 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
|
|
||||||
it("runs all startup checks on normal session.created", async () => {
|
it("runs all startup checks on normal session.created", async () => {
|
||||||
//#given - normal mode and no local dev version
|
//#given - normal mode and no local dev version
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {}, {
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput())
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
//#when - session.created event arrives on primary session
|
//#when - session.created event arrives on primary session
|
||||||
runSessionCreatedEvent(hook)
|
runSessionCreatedEvent(hook)
|
||||||
@@ -156,8 +123,18 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
|
|
||||||
it("ignores subagent sessions (parentID present)", async () => {
|
it("ignores subagent sessions (parentID present)", async () => {
|
||||||
//#given - a subagent session with parentID
|
//#given - a subagent session with parentID
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {}, {
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput())
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
//#when - session.created event contains parentID
|
//#when - session.created event contains parentID
|
||||||
runSessionCreatedEvent(hook, { info: { parentID: "parent-123" } })
|
runSessionCreatedEvent(hook, { info: { parentID: "parent-123" } })
|
||||||
@@ -175,8 +152,18 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
|
|
||||||
it("runs only once (hasChecked guard)", async () => {
|
it("runs only once (hasChecked guard)", async () => {
|
||||||
//#given - one hook instance in normal mode
|
//#given - one hook instance in normal mode
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {}, {
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput())
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
//#when - session.created event is fired twice
|
//#when - session.created event is fired twice
|
||||||
runSessionCreatedEvent(hook)
|
runSessionCreatedEvent(hook)
|
||||||
@@ -195,8 +182,18 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
it("shows localDevToast when local dev version exists", async () => {
|
it("shows localDevToast when local dev version exists", async () => {
|
||||||
//#given - local dev version is present
|
//#given - local dev version is present
|
||||||
mockGetLocalDevVersion.mockReturnValue("3.6.0-dev")
|
mockGetLocalDevVersion.mockReturnValue("3.6.0-dev")
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {}, {
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput())
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
//#when - session.created event arrives
|
//#when - session.created event arrives
|
||||||
runSessionCreatedEvent(hook)
|
runSessionCreatedEvent(hook)
|
||||||
@@ -214,8 +211,18 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
|
|
||||||
it("ignores non-session.created events", async () => {
|
it("ignores non-session.created events", async () => {
|
||||||
//#given - a hook instance in normal mode
|
//#given - a hook instance in normal mode
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {}, {
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput())
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
|
})
|
||||||
|
|
||||||
//#when - a non-session.created event arrives
|
//#when - a non-session.created event arrives
|
||||||
hook.event({
|
hook.event({
|
||||||
@@ -237,9 +244,19 @@ describe("createAutoUpdateCheckerHook", () => {
|
|||||||
|
|
||||||
it("passes correct toast message with sisyphus enabled", async () => {
|
it("passes correct toast message with sisyphus enabled", async () => {
|
||||||
//#given - sisyphus mode enabled
|
//#given - sisyphus mode enabled
|
||||||
const createAutoUpdateCheckerHook = await importFreshHookFactory()
|
|
||||||
const hook = createAutoUpdateCheckerHook(createPluginInput(), {
|
const hook = createAutoUpdateCheckerHook(createPluginInput(), {
|
||||||
isSisyphusEnabled: true,
|
isSisyphusEnabled: true,
|
||||||
|
}, {
|
||||||
|
getCachedVersion: mockGetCachedVersion,
|
||||||
|
getLocalDevVersion: mockGetLocalDevVersion,
|
||||||
|
showConfigErrorsIfAny: mockShowConfigErrorsIfAny,
|
||||||
|
updateAndShowConnectedProvidersCacheStatus: mockUpdateAndShowConnectedProvidersCacheStatus,
|
||||||
|
refreshModelCapabilitiesOnStartup: mockRefreshModelCapabilitiesOnStartup,
|
||||||
|
showModelCacheWarningIfNeeded: mockShowModelCacheWarningIfNeeded,
|
||||||
|
showLocalDevToast: mockShowLocalDevToast,
|
||||||
|
showVersionToast: mockShowVersionToast,
|
||||||
|
runBackgroundUpdateCheck: mockRunBackgroundUpdateCheck,
|
||||||
|
log: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when - session.created event arrives
|
//#when - session.created event arrives
|
||||||
|
|||||||
@@ -2,59 +2,40 @@ import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun
|
|||||||
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
import type { PluginEntryInfo } from "../auto-update-checker/checker/plugin-entry"
|
import type { PluginEntryInfo } from "../auto-update-checker/checker/plugin-entry"
|
||||||
|
import { CACHE_DIR } from "../auto-update-checker/constants"
|
||||||
|
|
||||||
const TEST_CACHE_DIR = join(import.meta.dir, "__test-sync-cache__")
|
const CACHE_PACKAGES_DIR = CACHE_DIR
|
||||||
|
const CACHE_PACKAGE_JSON_PATH = join(CACHE_PACKAGES_DIR, "package.json")
|
||||||
|
const ORIGINAL_CACHE_PACKAGE_JSON = existsSync(CACHE_PACKAGE_JSON_PATH)
|
||||||
|
? readFileSync(CACHE_PACKAGE_JSON_PATH, "utf-8")
|
||||||
|
: null
|
||||||
|
|
||||||
let importCounter = 0
|
let importCounter = 0
|
||||||
|
|
||||||
// Capture real modules BEFORE mocking
|
|
||||||
const _realConstants = require("../auto-update-checker/constants")
|
|
||||||
const _realLogger = require("../../shared/logger")
|
|
||||||
const _realNodeFs = require("node:fs")
|
|
||||||
|
|
||||||
async function importFreshSyncPackageJsonModule(): Promise<typeof import("../auto-update-checker/checker/sync-package-json")> {
|
async function importFreshSyncPackageJsonModule(): Promise<typeof import("../auto-update-checker/checker/sync-package-json")> {
|
||||||
mock.module("../auto-update-checker/constants", () => ({
|
|
||||||
CACHE_DIR: TEST_CACHE_DIR,
|
|
||||||
PACKAGE_NAME: "oh-my-opencode",
|
|
||||||
NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags",
|
|
||||||
NPM_FETCH_TIMEOUT: 5000,
|
|
||||||
VERSION_FILE: join(TEST_CACHE_DIR, "version"),
|
|
||||||
INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"),
|
|
||||||
getUserConfigDir: () => "/tmp/opencode-config",
|
|
||||||
getUserOpencodeConfig: () => "/tmp/opencode-config/opencode.json",
|
|
||||||
getUserOpencodeConfigJsonc: () => "/tmp/opencode-config/opencode.jsonc",
|
|
||||||
getWindowsAppdataDir: () => null,
|
|
||||||
}))
|
|
||||||
|
|
||||||
mock.module("../../shared/logger", () => ({
|
mock.module("../../shared/logger", () => ({
|
||||||
log: () => {},
|
log: () => {},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const syncPackageJsonModule = await import(`../auto-update-checker/checker/sync-package-json?test=${importCounter++}`)
|
return import(`../auto-update-checker/checker/sync-package-json?test=${importCounter++}`)
|
||||||
mock.restore()
|
|
||||||
return syncPackageJsonModule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetTestCache(currentVersion = "3.10.0"): void {
|
function resetTestCache(currentVersion = "3.10.0"): void {
|
||||||
if (existsSync(TEST_CACHE_DIR)) {
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
rmSync(TEST_CACHE_DIR, { recursive: true, force: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
mkdirSync(TEST_CACHE_DIR, { recursive: true })
|
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(TEST_CACHE_DIR, "package.json"),
|
CACHE_PACKAGE_JSON_PATH,
|
||||||
JSON.stringify({ dependencies: { "oh-my-opencode": currentVersion, other: "1.0.0" } }, null, 2)
|
JSON.stringify({ dependencies: { "oh-my-opencode": currentVersion, other: "1.0.0" } }, null, 2)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupTestCache(): void {
|
function cleanupTestCache(): void {
|
||||||
if (existsSync(TEST_CACHE_DIR)) {
|
if (existsSync(CACHE_PACKAGE_JSON_PATH)) {
|
||||||
rmSync(TEST_CACHE_DIR, { recursive: true, force: true })
|
rmSync(CACHE_PACKAGE_JSON_PATH, { force: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function readCachePackageJsonVersion(): string | undefined {
|
function readCachePackageJsonVersion(): string | undefined {
|
||||||
const content = readFileSync(join(TEST_CACHE_DIR, "package.json"), "utf-8")
|
const content = readFileSync(CACHE_PACKAGE_JSON_PATH, "utf-8")
|
||||||
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
||||||
return pkg.dependencies?.["oh-my-opencode"]
|
return pkg.dependencies?.["oh-my-opencode"]
|
||||||
}
|
}
|
||||||
@@ -65,6 +46,7 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
mock.restore()
|
||||||
cleanupTestCache()
|
cleanupTestCache()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -170,9 +152,9 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
describe("#given plugin not in cache package.json dependencies", () => {
|
describe("#given plugin not in cache package.json dependencies", () => {
|
||||||
it("#then adds the plugin dependency and preserves existing dependencies", async () => {
|
it("#then adds the plugin dependency and preserves existing dependencies", async () => {
|
||||||
cleanupTestCache()
|
cleanupTestCache()
|
||||||
mkdirSync(TEST_CACHE_DIR, { recursive: true })
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(TEST_CACHE_DIR, "package.json"),
|
join(CACHE_PACKAGES_DIR, "package.json"),
|
||||||
JSON.stringify({ dependencies: { other: "1.0.0" } }, null, 2)
|
JSON.stringify({ dependencies: { other: "1.0.0" } }, null, 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -190,10 +172,10 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
expect(result.synced).toBe(true)
|
expect(result.synced).toBe(true)
|
||||||
expect(result.error).toBeNull()
|
expect(result.error).toBeNull()
|
||||||
|
|
||||||
const content = readFileSync(join(TEST_CACHE_DIR, "package.json"), "utf-8")
|
const content = readFileSync(join(CACHE_PACKAGES_DIR, "package.json"), "utf-8")
|
||||||
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
||||||
expect(pkg.dependencies?.["oh-my-opencode"]).toBe("latest")
|
expect(pkg.dependencies?.["oh-my-opencode"]).toBe("latest")
|
||||||
expect(pkg.dependencies?.other).toBe("1.0.0")
|
expect(pkg.dependencies?.other).toBe("1.0.0")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -233,17 +215,17 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
expect(result.synced).toBe(true)
|
expect(result.synced).toBe(true)
|
||||||
expect(result.error).toBeNull()
|
expect(result.error).toBeNull()
|
||||||
|
|
||||||
const content = readFileSync(join(TEST_CACHE_DIR, "package.json"), "utf-8")
|
const content = readFileSync(join(CACHE_PACKAGES_DIR, "package.json"), "utf-8")
|
||||||
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
const pkg = JSON.parse(content) as { dependencies?: Record<string, string> }
|
||||||
expect(pkg.dependencies?.["other"]).toBe("1.0.0")
|
expect(pkg.dependencies?.["other"]).toBe("1.0.0")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("#given malformed JSON in cache package.json", () => {
|
describe("#given malformed JSON in cache package.json", () => {
|
||||||
it("#then returns parse_error", async () => {
|
it("#then returns parse_error", async () => {
|
||||||
cleanupTestCache()
|
cleanupTestCache()
|
||||||
mkdirSync(TEST_CACHE_DIR, { recursive: true })
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
writeFileSync(join(TEST_CACHE_DIR, "package.json"), "{ invalid json }")
|
writeFileSync(join(CACHE_PACKAGES_DIR, "package.json"), "{ invalid json }")
|
||||||
|
|
||||||
const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule()
|
const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule()
|
||||||
|
|
||||||
@@ -264,9 +246,9 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
describe("#given write permission denied", () => {
|
describe("#given write permission denied", () => {
|
||||||
it("#then returns write_error", async () => {
|
it("#then returns write_error", async () => {
|
||||||
cleanupTestCache()
|
cleanupTestCache()
|
||||||
mkdirSync(TEST_CACHE_DIR, { recursive: true })
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(TEST_CACHE_DIR, "package.json"),
|
join(CACHE_PACKAGES_DIR, "package.json"),
|
||||||
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2)
|
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -309,9 +291,9 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
describe("#given rename fails after successful write", () => {
|
describe("#given rename fails after successful write", () => {
|
||||||
it("#then returns write_error and cleans up temp file", async () => {
|
it("#then returns write_error and cleans up temp file", async () => {
|
||||||
cleanupTestCache()
|
cleanupTestCache()
|
||||||
mkdirSync(TEST_CACHE_DIR, { recursive: true })
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(TEST_CACHE_DIR, "package.json"),
|
join(CACHE_PACKAGES_DIR, "package.json"),
|
||||||
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2)
|
JSON.stringify({ dependencies: { "oh-my-opencode": "3.10.0" } }, null, 2)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -360,8 +342,11 @@ describe("syncCachePackageJsonToIntent", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
mock.module("../auto-update-checker/constants", () => _realConstants)
|
if (ORIGINAL_CACHE_PACKAGE_JSON === null) {
|
||||||
mock.module("../../shared/logger", () => _realLogger)
|
cleanupTestCache()
|
||||||
mock.module("node:fs", () => _realNodeFs)
|
} else {
|
||||||
|
mkdirSync(CACHE_PACKAGES_DIR, { recursive: true })
|
||||||
|
writeFileSync(CACHE_PACKAGE_JSON_PATH, ORIGINAL_CACHE_PACKAGE_JSON)
|
||||||
|
}
|
||||||
mock.restore()
|
mock.restore()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user