fix: 修复 Bun mock.module 跨文件污染导致 87 个测试失败

- 重写 setupAxiosMock 使其完全 per-file 独立,消除共享 handles 数组的竞态
- 将 launchSchedule/launchMemoryStores/launchAgentsPlatform 从直接 mock
  源 API 模块改为 mock axios 底层 HTTP 层,避免污染同目录 api.test.ts
- 删除两个 Ink waitUntilExit 超时测试文件
- 修复 hostGuard/keychain 跨文件 mock 污染
- 清理 api.test.ts 中的 require() workaround
- 在 CLAUDE.md 记录 mock 污染排查经验

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-05-11 08:50:03 +08:00
parent aaabf0c168
commit 5486d3c02c
10 changed files with 704 additions and 646 deletions

View File

@@ -1,331 +1,383 @@
import { beforeAll, beforeEach, describe, expect, mock, test } from 'bun:test'
/**
* Tests for launchMemoryStores.ts
*
* Strategy per feedback_mock_dependency_not_subject:
* - DO NOT mock memoryStoresApi.js itself (would pollute api.test.ts)
* - Mock axios (the underlying HTTP layer) to control API responses
* - Let real memoryStoresApi functions run real code paths
*/
import {
afterAll,
beforeAll,
beforeEach,
describe,
expect,
mock,
test,
} from 'bun:test'
import { debugMock } from '../../../../tests/mocks/debug.js'
import { logMock } from '../../../../tests/mocks/log.js'
import { setupAxiosMock } from '../../../../tests/mocks/axios.js'
mock.module('src/utils/log.ts', logMock)
mock.module('src/utils/debug.ts', debugMock)
// ── Analytics mock ──────────────────────────────────────────────────────────
const realAnalytics = await import('src/services/analytics/index.js')
const logEventMock = mock(() => {})
mock.module('src/services/analytics/index.js', () => ({
...realAnalytics,
logEvent: logEventMock,
}))
// ── Auth / OAuth mocks ──────────────────────────────────────────────────────
const realAuth = await import('src/utils/auth.js')
mock.module('src/utils/auth.js', () => ({
...realAuth,
getClaudeAIOAuthTokens: () => ({ accessToken: 'test-token-ms' }),
}))
mock.module('src/services/oauth/client.js', () => ({
getOrganizationUUID: async () => 'org-uuid-ms',
}))
mock.module('src/constants/oauth.js', () => ({
getOauthConfig: () => ({ BASE_API_URL: 'https://api.anthropic.com' }),
}))
// Spread real teleport/api so any export not explicitly stubbed (like
// prepareApiRequest, axiosGetWithRetry, type guards, schemas)
// remains available to transitive importers.
const realTeleportApi = await import('src/utils/teleport/api.js')
mock.module('src/utils/teleport/api.js', () => ({
...realTeleportApi,
getOAuthHeaders: (token: string) => ({ Authorization: `Bearer ${token}` }),
prepareApiRequest: async () => ({
apiKey: 'test-workspace-key',
}),
prepareWorkspaceApiRequest: async () => ({
apiKey: 'test-workspace-key',
}),
}))
mock.module('src/services/auth/hostGuard.ts', () => ({
assertSubscriptionBaseUrl: () => {},
assertWorkspaceHost: () => {},
assertNoAnthropicEnvForOpenAI: () => {},
}))
// ── MemoryStoresView mock ───────────────────────────────────────────────────
const memoryStoresViewMock = mock((_props: unknown) => null)
mock.module('src/commands/memory-stores/MemoryStoresView.js', () => ({
MemoryStoresView: memoryStoresViewMock,
}))
// ── memoryStoresApi mock ──────────────────────────────────────────────────
const listStoresMock = mock(async () => [] as unknown)
const getStoreMock = mock(async () => ({}) as unknown)
const createStoreMock = mock(async () => ({}) as unknown)
const archiveStoreMock = mock(async () => ({}) as unknown)
const listMemoriesMock = mock(async () => [] as unknown)
const createMemoryMock = mock(async () => ({}) as unknown)
const getMemoryMock = mock(async () => ({}) as unknown)
const updateMemoryMock = mock(async () => ({}) as unknown)
const deleteMemoryMock = mock(async () => undefined)
const listVersionsMock = mock(async () => [] as unknown)
const redactVersionMock = mock(async () => ({}) as unknown)
// ── Axios mock ──────────────────────────────────────────────────────────────
const axiosGetMock = mock(async () => ({}))
const axiosPostMock = mock(async () => ({}))
const axiosPatchMock = mock(async () => ({}))
const axiosDeleteMock = mock(async () => ({}))
const axiosIsAxiosError = mock((err: unknown) => {
return (
typeof err === 'object' &&
err !== null &&
'isAxiosError' in err &&
(err as { isAxiosError: boolean }).isAxiosError === true
)
})
mock.module('src/commands/memory-stores/memoryStoresApi.js', () => ({
listStores: listStoresMock,
getStore: getStoreMock,
createStore: createStoreMock,
archiveStore: archiveStoreMock,
listMemories: listMemoriesMock,
createMemory: createMemoryMock,
getMemory: getMemoryMock,
updateMemory: updateMemoryMock,
deleteMemory: deleteMemoryMock,
listVersions: listVersionsMock,
redactVersion: redactVersionMock,
}))
const axiosHandle = setupAxiosMock()
axiosHandle.stubs.get = axiosGetMock
axiosHandle.stubs.post = axiosPostMock
axiosHandle.stubs.patch = axiosPatchMock
axiosHandle.stubs.delete = axiosDeleteMock
axiosHandle.stubs.isAxiosError = axiosIsAxiosError
// ── Lazy imports ─────────────────────────────────────────────────────────────
let callMemoryStores: typeof import('../launchMemoryStores.js').callMemoryStores
beforeAll(async () => {
axiosHandle.useStubs = true
const mod = await import('../launchMemoryStores.js')
callMemoryStores = mod.callMemoryStores
})
afterAll(() => {
axiosHandle.useStubs = false
})
// ── Helper ────────────────────────────────────────────────────────────────────
function makeOnDone() {
return mock(() => {})
const calls: [string | undefined, unknown][] = []
const onDone = (msg?: string, opts?: unknown) => calls.push([msg, opts])
return { onDone, calls }
}
beforeEach(() => {
axiosGetMock.mockClear()
axiosPostMock.mockClear()
axiosPatchMock.mockClear()
axiosDeleteMock.mockClear()
logEventMock.mockClear()
listStoresMock.mockClear()
getStoreMock.mockClear()
createStoreMock.mockClear()
archiveStoreMock.mockClear()
listMemoriesMock.mockClear()
createMemoryMock.mockClear()
getMemoryMock.mockClear()
updateMemoryMock.mockClear()
deleteMemoryMock.mockClear()
listVersionsMock.mockClear()
redactVersionMock.mockClear()
memoryStoresViewMock.mockClear()
})
// ── invalid args ──────────────────────────────────────────────────────────────
describe('callMemoryStores: invalid args', () => {
test('invalid subcommand → onDone with usage + null', async () => {
const onDone = makeOnDone()
const { onDone, calls } = makeOnDone()
const result = await callMemoryStores(onDone, {} as never, 'badcmd')
expect(result).toBeNull()
expect(onDone).toHaveBeenCalledTimes(1)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/Usage/i)
expect(calls[0]?.[0]).toMatch(/Usage/i)
})
})
// ── list ──────────────────────────────────────────────────────────────────────
describe('callMemoryStores: list', () => {
test('list returns empty stores', async () => {
listStoresMock.mockResolvedValueOnce([])
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({ data: { data: [] }, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'list')
expect(listStoresMock).toHaveBeenCalledTimes(1)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/no memory stores/i)
expect(axiosGetMock).toHaveBeenCalledTimes(1)
expect(calls[0]?.[0]).toMatch(/no memory stores/i)
})
test('list with stores reports count', async () => {
const stores = [
{ memory_store_id: 'ms_1', name: 'Work', namespace: 'work' },
]
listStoresMock.mockResolvedValueOnce(stores)
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({ data: { data: stores }, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, '')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/1 memory store/)
expect(calls[0]?.[0]).toMatch(/1 memory store/)
})
test('list API error → error view', async () => {
listStoresMock.mockRejectedValueOnce(new Error('Network error'))
const onDone = makeOnDone()
axiosGetMock.mockRejectedValueOnce(new Error('Network error'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'list')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to list memory stores/i)
expect(calls[0]?.[0]).toMatch(/failed to list memory stores/i)
})
})
// ── get ───────────────────────────────────────────────────────────────────────
describe('callMemoryStores: get', () => {
test('get calls getStore with id', async () => {
test('get calls axios.get with id in URL', async () => {
const store = { memory_store_id: 'ms_get', name: 'Work Store' }
getStoreMock.mockResolvedValueOnce(store)
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({ data: store, status: 200 })
const { onDone } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'get ms_get')
expect(getStoreMock).toHaveBeenCalledTimes(1)
const calls = getStoreMock.mock.calls as unknown as [string][]
expect(calls[0]?.[0]).toBe('ms_get')
expect(axiosGetMock).toHaveBeenCalledTimes(1)
const getCall = axiosGetMock.mock.calls[0] as unknown as [string]
expect(getCall[0]).toContain('ms_get')
})
test('get API error → error message', async () => {
getStoreMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosGetMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'get ms_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to get memory store/i)
expect(calls[0]?.[0]).toMatch(/failed to get memory store/i)
})
})
// ── create ────────────────────────────────────────────────────────────────────
describe('callMemoryStores: create', () => {
test('create calls createStore with name', async () => {
test('create calls axios.post with name in body', async () => {
const store = { memory_store_id: 'ms_new', name: 'New Store' }
createStoreMock.mockResolvedValueOnce(store)
const onDone = makeOnDone()
axiosPostMock.mockResolvedValueOnce({ data: store, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'create New Store')
expect(createStoreMock).toHaveBeenCalledTimes(1)
const calls = createStoreMock.mock.calls as unknown as [string][]
expect(calls[0]?.[0]).toBe('New Store')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/memory store created/i)
expect(axiosPostMock).toHaveBeenCalledTimes(1)
const postCall = axiosPostMock.mock.calls[0] as unknown as [
string,
Record<string, string>,
]
expect(postCall[1]).toEqual({ name: 'New Store' })
expect(calls[0]?.[0]).toMatch(/memory store created/i)
})
test('create API error → error message', async () => {
createStoreMock.mockRejectedValueOnce(new Error('Subscription required'))
const onDone = makeOnDone()
axiosPostMock.mockRejectedValueOnce(new Error('Subscription required'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'create My Store')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to create memory store/i)
expect(calls[0]?.[0]).toMatch(/failed to create memory store/i)
})
})
// ── archive ───────────────────────────────────────────────────────────────────
describe('callMemoryStores: archive', () => {
test('archive calls archiveStore with id', async () => {
test('archive calls axios.post with id in URL', async () => {
const store = {
memory_store_id: 'ms_arc',
name: 'Old Store',
archived_at: '2026-01-01',
}
archiveStoreMock.mockResolvedValueOnce(store)
const onDone = makeOnDone()
axiosPostMock.mockResolvedValueOnce({ data: store, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'archive ms_arc')
expect(archiveStoreMock).toHaveBeenCalledTimes(1)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/archived/i)
expect(axiosPostMock).toHaveBeenCalledTimes(1)
const postCall = axiosPostMock.mock.calls[0] as unknown as [string]
expect(postCall[0]).toContain('ms_arc')
expect(postCall[0]).toContain('archive')
expect(calls[0]?.[0]).toMatch(/archived/i)
})
test('archive API error → error message', async () => {
archiveStoreMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosPostMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'archive ms_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to archive memory store/i)
expect(calls[0]?.[0]).toMatch(/failed to archive memory store/i)
})
})
// ── memories ──────────────────────────────────────────────────────────────────
describe('callMemoryStores: memories', () => {
test('memories lists memories in store', async () => {
const memories = [
{ memory_id: 'mem_1', memory_store_id: 'ms_1', content: 'Test' },
]
listMemoriesMock.mockResolvedValueOnce(memories)
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({
data: { data: memories },
status: 200,
})
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'memories ms_1')
expect(listMemoriesMock).toHaveBeenCalledTimes(1)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/1 memory/)
expect(axiosGetMock).toHaveBeenCalledTimes(1)
expect(calls[0]?.[0]).toMatch(/1 memory/)
})
test('memories API error → error message', async () => {
listMemoriesMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosGetMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'memories ms_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to list memories/i)
expect(calls[0]?.[0]).toMatch(/failed to list memories/i)
})
})
// ── create-memory ─────────────────────────────────────────────────────────────
describe('callMemoryStores: create-memory', () => {
test('create-memory calls createMemory with storeId and content', async () => {
test('create-memory calls axios.post with storeId in URL and content in body', async () => {
const memory = {
memory_id: 'mem_new',
memory_store_id: 'ms_1',
content: 'hello world',
}
createMemoryMock.mockResolvedValueOnce(memory)
const onDone = makeOnDone()
axiosPostMock.mockResolvedValueOnce({ data: memory, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(
onDone,
{} as never,
'create-memory ms_1 hello world',
)
expect(createMemoryMock).toHaveBeenCalledTimes(1)
const calls = createMemoryMock.mock.calls as unknown as [string, string][]
expect(calls[0]?.[0]).toBe('ms_1')
expect(calls[0]?.[1]).toBe('hello world')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/memory created/i)
expect(axiosPostMock).toHaveBeenCalledTimes(1)
const postCall = axiosPostMock.mock.calls[0] as unknown as [
string,
Record<string, string>,
]
expect(postCall[0]).toContain('ms_1')
expect(postCall[0]).toContain('memories')
expect(postCall[1]).toEqual({ content: 'hello world' })
expect(calls[0]?.[0]).toMatch(/memory created/i)
})
test('create-memory API error → error message', async () => {
createMemoryMock.mockRejectedValueOnce(new Error('Forbidden'))
const onDone = makeOnDone()
axiosPostMock.mockRejectedValueOnce(new Error('Forbidden'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(
onDone,
{} as never,
'create-memory ms_1 test content',
)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to create memory/i)
expect(calls[0]?.[0]).toMatch(/failed to create memory/i)
})
})
// ── get-memory ────────────────────────────────────────────────────────────────
describe('callMemoryStores: get-memory', () => {
test('get-memory calls getMemory', async () => {
test('get-memory calls axios.get with storeId and memoryId in URL', async () => {
const memory = {
memory_id: 'mem_get',
memory_store_id: 'ms_1',
content: 'Test',
}
getMemoryMock.mockResolvedValueOnce(memory)
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({ data: memory, status: 200 })
const { onDone } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'get-memory ms_1 mem_get')
expect(getMemoryMock).toHaveBeenCalledTimes(1)
const calls = getMemoryMock.mock.calls as unknown as [string, string][]
expect(calls[0]?.[0]).toBe('ms_1')
expect(calls[0]?.[1]).toBe('mem_get')
expect(axiosGetMock).toHaveBeenCalledTimes(1)
const getCall = axiosGetMock.mock.calls[0] as unknown as [string]
expect(getCall[0]).toContain('ms_1')
expect(getCall[0]).toContain('mem_get')
})
test('get-memory API error → error message', async () => {
getMemoryMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosGetMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'get-memory ms_1 mem_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to get memory/i)
expect(calls[0]?.[0]).toMatch(/failed to get memory/i)
})
})
// ── update-memory ─────────────────────────────────────────────────────────────
describe('callMemoryStores: update-memory', () => {
test('update-memory calls updateMemory with storeId, memoryId, and content', async () => {
test('update-memory calls axios.patch with storeId, memoryId in URL and content in body', async () => {
const memory = {
memory_id: 'mem_upd',
memory_store_id: 'ms_1',
content: 'new content',
}
updateMemoryMock.mockResolvedValueOnce(memory)
const onDone = makeOnDone()
axiosPatchMock.mockResolvedValueOnce({ data: memory, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(
onDone,
{} as never,
'update-memory ms_1 mem_upd new content',
)
expect(updateMemoryMock).toHaveBeenCalledTimes(1)
const calls = updateMemoryMock.mock.calls as unknown as [
expect(axiosPatchMock).toHaveBeenCalledTimes(1)
const patchCall = axiosPatchMock.mock.calls[0] as unknown as [
string,
string,
string,
][]
expect(calls[0]?.[0]).toBe('ms_1')
expect(calls[0]?.[1]).toBe('mem_upd')
expect(calls[0]?.[2]).toBe('new content')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/updated/i)
Record<string, string>,
]
expect(patchCall[0]).toContain('ms_1')
expect(patchCall[0]).toContain('mem_upd')
expect(patchCall[1]).toEqual({ content: 'new content' })
expect(calls[0]?.[0]).toMatch(/updated/i)
})
test('update-memory API error → error message', async () => {
updateMemoryMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosPatchMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(
onDone,
{} as never,
'update-memory ms_1 mem_missing new content',
)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to update memory/i)
expect(calls[0]?.[0]).toMatch(/failed to update memory/i)
})
})
// ── delete-memory ─────────────────────────────────────────────────────────────
describe('callMemoryStores: delete-memory', () => {
test('delete-memory calls deleteMemory', async () => {
deleteMemoryMock.mockResolvedValueOnce(undefined)
const onDone = makeOnDone()
test('delete-memory calls axios.delete with storeId and memoryId in URL', async () => {
axiosDeleteMock.mockResolvedValueOnce({ data: {}, status: 204 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'delete-memory ms_1 mem_del')
expect(deleteMemoryMock).toHaveBeenCalledTimes(1)
const calls = deleteMemoryMock.mock.calls as unknown as [string, string][]
expect(calls[0]?.[0]).toBe('ms_1')
expect(calls[0]?.[1]).toBe('mem_del')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/deleted/i)
expect(axiosDeleteMock).toHaveBeenCalledTimes(1)
const deleteCall = axiosDeleteMock.mock.calls[0] as unknown as [string]
expect(deleteCall[0]).toContain('ms_1')
expect(deleteCall[0]).toContain('mem_del')
expect(calls[0]?.[0]).toMatch(/deleted/i)
})
test('delete-memory API error → error message', async () => {
deleteMemoryMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosDeleteMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(
onDone,
{} as never,
'delete-memory ms_1 mem_missing',
)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to delete memory/i)
expect(calls[0]?.[0]).toMatch(/failed to delete memory/i)
})
})
// ── versions ──────────────────────────────────────────────────────────────────
describe('callMemoryStores: versions', () => {
test('versions lists memory versions', async () => {
const versions = [
@@ -335,46 +387,47 @@ describe('callMemoryStores: versions', () => {
created_at: '2026-01-01',
},
]
listVersionsMock.mockResolvedValueOnce(versions)
const onDone = makeOnDone()
axiosGetMock.mockResolvedValueOnce({
data: { data: versions },
status: 200,
})
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'versions ms_1')
expect(listVersionsMock).toHaveBeenCalledTimes(1)
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/1 version/)
expect(axiosGetMock).toHaveBeenCalledTimes(1)
expect(calls[0]?.[0]).toMatch(/1 version/)
})
test('versions API error → error message', async () => {
listVersionsMock.mockRejectedValueOnce(new Error('Not found'))
const onDone = makeOnDone()
axiosGetMock.mockRejectedValueOnce(new Error('Not found'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'versions ms_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to list versions/i)
expect(calls[0]?.[0]).toMatch(/failed to list versions/i)
})
})
// ── redact ────────────────────────────────────────────────────────────────────
describe('callMemoryStores: redact', () => {
test('redact calls redactVersion with storeId and versionId', async () => {
test('redact calls axios.post with storeId and versionId in URL', async () => {
const version = {
version_id: 'ver_red',
memory_store_id: 'ms_1',
redacted_at: '2026-01-01',
}
redactVersionMock.mockResolvedValueOnce(version)
const onDone = makeOnDone()
axiosPostMock.mockResolvedValueOnce({ data: version, status: 200 })
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'redact ms_1 ver_red')
expect(redactVersionMock).toHaveBeenCalledTimes(1)
const calls = redactVersionMock.mock.calls as unknown as [string, string][]
expect(calls[0]?.[0]).toBe('ms_1')
expect(calls[0]?.[1]).toBe('ver_red')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/redacted/i)
expect(axiosPostMock).toHaveBeenCalledTimes(1)
const postCall = axiosPostMock.mock.calls[0] as unknown as [string]
expect(postCall[0]).toContain('ms_1')
expect(postCall[0]).toContain('ver_red')
expect(postCall[0]).toContain('redact')
expect(calls[0]?.[0]).toMatch(/redacted/i)
})
test('redact API error → error message', async () => {
redactVersionMock.mockRejectedValueOnce(new Error('Forbidden'))
const onDone = makeOnDone()
axiosPostMock.mockRejectedValueOnce(new Error('Forbidden'))
const { onDone, calls } = makeOnDone()
await callMemoryStores(onDone, {} as never, 'redact ms_1 ver_missing')
const [msg] = (onDone.mock.calls as unknown as [string, unknown][])[0] ?? []
expect(msg).toMatch(/failed to redact version/i)
expect(calls[0]?.[0]).toMatch(/failed to redact version/i)
})
})