Files
oh-my-opencode/src/shared/opencode-http-api.test.ts
T

192 lines
5.7 KiB
TypeScript
Raw Normal View History

2026-05-15 16:26:57 +09:00
import { describe, it, expect, mock, beforeEach } from "bun:test"
2026-05-15 16:26:57 +09:00
type OpencodeHttpApi = typeof import("./opencode-http-api")
2026-05-15 16:26:57 +09:00
const opencodeHttpApiSpecifier = import.meta.resolve("./opencode-http-api")
2026-05-15 16:26:57 +09:00
const log = mock(() => {})
const getServerBasicAuthHeader = mock(() => "Basic b3BlbmNvZGU6dGVzdHBhc3N3b3Jk")
const fetchImplementation = mock(async (): Promise<Response> => new Response(null, { status: 200 }))
async function loadOpencodeHttpApi(): Promise<OpencodeHttpApi> {
const opencodeHttpApi = await import(`${opencodeHttpApiSpecifier}?test=${crypto.randomUUID()}`)
opencodeHttpApi._setFetchImplementationForTesting(fetchImplementation)
opencodeHttpApi._setLogImplementationForTesting(log)
opencodeHttpApi._setServerBasicAuthHeaderResolverForTesting(getServerBasicAuthHeader)
return opencodeHttpApi
}
describe("getServerBaseUrl", () => {
2026-05-15 16:26:57 +09:00
it("returns baseUrl from client._client.getConfig().baseUrl", async () => {
// given
2026-05-15 16:26:57 +09:00
const { getServerBaseUrl } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({ baseUrl: "https://api.example.com" }),
},
}
// when
const result = getServerBaseUrl(mockClient)
// then
expect(result).toBe("https://api.example.com")
})
2026-05-15 16:26:57 +09:00
it("returns baseUrl from client.session._client.getConfig().baseUrl when first attempt fails", async () => {
// given
2026-05-15 16:26:57 +09:00
const { getServerBaseUrl } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({}),
},
session: {
_client: {
getConfig: () => ({ baseUrl: "https://session.example.com" }),
},
},
}
// when
const result = getServerBaseUrl(mockClient)
// then
expect(result).toBe("https://session.example.com")
})
2026-05-15 16:26:57 +09:00
it("returns null for incompatible client", async () => {
// given
2026-05-15 16:26:57 +09:00
const { getServerBaseUrl } = await loadOpencodeHttpApi()
const mockClient = {}
// when
const result = getServerBaseUrl(mockClient)
// then
expect(result).toBeNull()
})
})
describe("patchPart", () => {
beforeEach(() => {
2026-05-15 16:26:57 +09:00
log.mockClear()
getServerBasicAuthHeader.mockClear()
getServerBasicAuthHeader.mockReturnValue("Basic b3BlbmNvZGU6dGVzdHBhc3N3b3Jk")
fetchImplementation.mockClear()
fetchImplementation.mockResolvedValue(new Response(null, { status: 200 }))
})
it("constructs correct URL and sends PATCH with auth", async () => {
// given
2026-05-15 16:26:57 +09:00
const { patchPart } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({ baseUrl: "https://api.example.com" }),
},
}
const sessionID = "ses123"
const messageID = "msg456"
const partID = "part789"
const body = { content: "test" }
// when
const result = await patchPart(mockClient, sessionID, messageID, partID, body)
// then
expect(result).toBe(true)
2026-05-15 16:26:57 +09:00
expect(fetchImplementation).toHaveBeenCalledWith(
"https://api.example.com/session/ses123/message/msg456/part/part789",
expect.objectContaining({
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": "Basic b3BlbmNvZGU6dGVzdHBhc3N3b3Jk",
},
body: JSON.stringify(body),
signal: expect.any(AbortSignal),
})
)
})
it("returns false on network error", async () => {
// given
2026-05-15 16:26:57 +09:00
const { patchPart } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({ baseUrl: "https://api.example.com" }),
},
}
2026-05-15 16:26:57 +09:00
fetchImplementation.mockRejectedValue(new Error("Network error"))
// when
const result = await patchPart(mockClient, "ses123", "msg456", "part789", {})
// then
expect(result).toBe(false)
expect(log).toHaveBeenCalledWith("[opencode-http-api] PATCH error", {
message: "Network error",
url: "https://api.example.com/session/ses123/message/msg456/part/part789",
})
})
})
describe("deletePart", () => {
beforeEach(() => {
2026-05-15 16:26:57 +09:00
log.mockClear()
getServerBasicAuthHeader.mockClear()
getServerBasicAuthHeader.mockReturnValue("Basic b3BlbmNvZGU6dGVzdHBhc3N3b3Jk")
fetchImplementation.mockClear()
fetchImplementation.mockResolvedValue(new Response(null, { status: 200 }))
})
it("constructs correct URL and sends DELETE", async () => {
// given
2026-05-15 16:26:57 +09:00
const { deletePart } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({ baseUrl: "https://api.example.com" }),
},
}
const sessionID = "ses123"
const messageID = "msg456"
const partID = "part789"
// when
const result = await deletePart(mockClient, sessionID, messageID, partID)
// then
expect(result).toBe(true)
2026-05-15 16:26:57 +09:00
expect(fetchImplementation).toHaveBeenCalledWith(
"https://api.example.com/session/ses123/message/msg456/part/part789",
expect.objectContaining({
method: "DELETE",
headers: {
"Authorization": "Basic b3BlbmNvZGU6dGVzdHBhc3N3b3Jk",
},
signal: expect.any(AbortSignal),
})
)
})
it("returns false on non-ok response", async () => {
// given
2026-05-15 16:26:57 +09:00
const { deletePart } = await loadOpencodeHttpApi()
const mockClient = {
_client: {
getConfig: () => ({ baseUrl: "https://api.example.com" }),
},
}
2026-05-15 16:26:57 +09:00
fetchImplementation.mockResolvedValue(new Response(null, { status: 404 }))
// when
const result = await deletePart(mockClient, "ses123", "msg456", "part789")
// then
expect(result).toBe(false)
expect(log).toHaveBeenCalledWith("[opencode-http-api] DELETE failed", {
status: 404,
url: "https://api.example.com/session/ses123/message/msg456/part/part789",
})
})
2026-05-15 16:26:57 +09:00
})