|
|
@@ -0,0 +1,74 @@
|
|
|
+/**
|
|
|
+ * Regression tests for the SDK error shape — the v2 SDK's `throwOnError: true`
|
|
|
+ * path used to throw raw values (empty strings or POJOs from JSON-decoded
|
|
|
+ * error bodies). The TUI catches those and `e.message`/`e.stack` are
|
|
|
+ * undefined, so users see `[object Object]` or a blank crash.
|
|
|
+ *
|
|
|
+ * Both cases must throw a real `Error` instance with a non-empty `.message`
|
|
|
+ * extracted from the response body, plus `.status` and `.body` attached.
|
|
|
+ */
|
|
|
+import { afterEach, describe, expect, test } from "bun:test"
|
|
|
+import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
|
|
+import { Server } from "../../src/server/server"
|
|
|
+import * as Log from "@opencode-ai/core/util/log"
|
|
|
+import { disposeAllInstances, tmpdir } from "../fixture/fixture"
|
|
|
+import { resetDatabase } from "../fixture/db"
|
|
|
+
|
|
|
+void Log.init({ print: false })
|
|
|
+
|
|
|
+afterEach(async () => {
|
|
|
+ await disposeAllInstances()
|
|
|
+ await resetDatabase()
|
|
|
+})
|
|
|
+
|
|
|
+function client(directory: string) {
|
|
|
+ return createOpencodeClient({
|
|
|
+ baseUrl: "http://test",
|
|
|
+ directory,
|
|
|
+ fetch: ((req: Request) => Server.Default().app.fetch(req)) as unknown as typeof fetch,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+describe("v2 SDK error shape", () => {
|
|
|
+ test("404 with NamedError body throws a real Error carrying the server message", async () => {
|
|
|
+ await using tmp = await tmpdir({ git: true, config: { formatter: false, lsp: false } })
|
|
|
+ const sdk = client(tmp.path)
|
|
|
+
|
|
|
+ let caught: unknown
|
|
|
+ try {
|
|
|
+ await sdk.session.get({ sessionID: "ses_no_such" }, { throwOnError: true })
|
|
|
+ } catch (e) {
|
|
|
+ caught = e
|
|
|
+ }
|
|
|
+
|
|
|
+ expect(caught).toBeInstanceOf(Error)
|
|
|
+ const err = caught as Error
|
|
|
+ const cause = err.cause as { body?: any; status?: number }
|
|
|
+ expect(err.message).toContain("Session not found")
|
|
|
+ expect(cause.status).toBe(404)
|
|
|
+ expect(cause.body).toMatchObject({
|
|
|
+ name: "NotFoundError",
|
|
|
+ data: { message: expect.stringContaining("Session not found") },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test("400 with empty body throws a real Error naming the status", async () => {
|
|
|
+ await using tmp = await tmpdir({ config: { formatter: false, lsp: false } })
|
|
|
+ const sdk = client(tmp.path)
|
|
|
+
|
|
|
+ let caught: unknown
|
|
|
+ try {
|
|
|
+ // POST /sync/history with `aggregate: -1` triggers schema validation
|
|
|
+ // that returns an empty 400 body (verified via plan-mode probe).
|
|
|
+ await sdk.sync.history.list({ aggregate: -1 } as any, { throwOnError: true })
|
|
|
+ } catch (e) {
|
|
|
+ caught = e
|
|
|
+ }
|
|
|
+
|
|
|
+ expect(caught).toBeInstanceOf(Error)
|
|
|
+ const err = caught as Error
|
|
|
+ const cause = err.cause as { status?: number }
|
|
|
+ expect(err.message.length).toBeGreaterThan(0)
|
|
|
+ expect(cause.status).toBe(400)
|
|
|
+ })
|
|
|
+})
|