import { describe, it, expect } from 'vitest' import { createD1Client } from '../../bin/d1-http.mjs' type Init = { method?: string; body?: string; headers?: Record } type Call = { url: string; init: Init } function fakeFetch(calls: Call[], rows: unknown[] = []) { return async (url: string, init: Init) => { calls.push({ url, init }) if (url.includes('?name=')) { return { ok: true, status: 200, json: async () => ({ result: [{ name: 'portal-db', uuid: 'db-uuid' }] }) } } return { ok: true, status: 200, json: async () => ({ result: [{ results: rows }] }) } } } const opts = (fetchFn: unknown) => ({ accountId: 'acc', token: 't', dbName: 'portal-db', fetchFn: fetchFn as never, }) describe('createD1Client', () => { it('resolves the database by exact name then queries by uuid', async () => { const calls: Call[] = [] const c = createD1Client(opts(fakeFetch(calls))) await c.query('SELECT 1') expect(calls[0].url).toContain('/accounts/acc/d1/database?name=portal-db') expect(calls[1].url).toContain('/d1/database/db-uuid/query') expect(calls[1].init.headers?.Authorization).toBe('Bearer t') }) it('sends values as params, never interpolated into sql', async () => { const calls: Call[] = [] const c = createD1Client(opts(fakeFetch(calls))) await c.query('INSERT INTO directory (relPath) VALUES (?)', ["o'brien.pdf"]) const body = JSON.parse(calls[1].init.body ?? '{}') expect(body.sql).toBe('INSERT INTO directory (relPath) VALUES (?)') expect(body.params).toEqual(["o'brien.pdf"]) }) it('returns the rows of the first result', async () => { const c = createD1Client(opts(fakeFetch([], [{ n: 3 }]))) expect(await c.query('SELECT COUNT(*) AS n FROM directory')).toEqual([{ n: 3 }]) }) it('caches the name lookup across statements', async () => { const calls: Call[] = [] const c = createD1Client(opts(fakeFetch(calls))) await c.query('SELECT 1') await c.query('SELECT 2') expect(calls.filter((x) => x.url.includes('?name=')).length).toBe(1) expect(c.statements).toBe(2) }) it('throws carrying the response body when the api fails', async () => { // Never discard an error body: a bare status turns every failure into the // same unactionable line. const fetchFn = async () => ({ ok: false, status: 403, json: async () => ({ errors: [{ message: 'insufficient permissions' }] }), }) const c = createD1Client(opts(fetchFn)) await expect(c.query('SELECT 1')).rejects.toThrow(/403.*insufficient permissions/) }) it('refuses a database whose name only nearly matches', async () => { // `?name=` is a filter whose matching rule is not documented, so taking the // first result on trust could run this account's statements against a // near-named database. const fetchFn = async (url: string) => ({ ok: true, status: 200, json: async () => url.includes('?name=') ? { result: [{ name: 'portal-db-old', uuid: 'x' }] } : { result: [] }, }) const c = createD1Client(opts(fetchFn)) await expect(c.query('SELECT 1')).rejects.toThrow(/no D1 database named/) }) it('counts a statement that was issued and then errored', async () => { let n = 0 const fetchFn = async (url: string) => { n++ if (url.includes('?name=')) { return { ok: true, status: 200, json: async () => ({ result: [{ name: 'portal-db', uuid: 'u' }] }) } } return { ok: false, status: 500, json: async () => ({ errors: [{ message: 'boom' }] }) } } const c = createD1Client(opts(fetchFn)) await expect(c.query('SELECT 1')).rejects.toThrow() expect(c.statements).toBe(1) }) })