import { describe, expect, it } from 'vitest' import { processDelete } from '../../skills/data-portal/template/functions/api/delete' import type { PortalEnv } from '../../skills/data-portal/template/functions/api/_lib/types' /** `row` is what the manifest holds for the key under test, or null when the * key is unknown. The fake tracks which of the three writes actually happened * so a test can assert the R2 object was never touched. */ function harness(row: { devicePath: string } | null) { const state = { objectDeleted: false, rowDeleted: false, marked: false } const lines: string[] = [] const env = { BUCKET: { async delete() { state.objectDeleted = true }, async head() { return state.objectDeleted ? null : { size: 1 } }, }, DB: { prepare(sql: string) { return { bind() { return { async first() { if (sql.includes('FROM sessions')) { return { ownerId: 'alice', accountId: 'acct', folders: '' } } if (sql.includes('SELECT devicePath')) return row if (sql.includes('SELECT deleted')) return { deleted: state.marked ? 1 : 0 } if (sql.includes('SELECT fileId FROM manifest')) { return state.rowDeleted || !row ? null : { fileId: 'f1' } } return null }, async run() { if (/^UPDATE manifest/.test(sql)) state.marked = true if (/^DELETE FROM manifest/.test(sql)) state.rowDeleted = true }, } }, } }, }, } as unknown as PortalEnv return { env, state, lines, log: (l: string) => lines.push(l) } } describe('delete branches on whether the file has moved', () => { it('deletes object and row outright when the file is still in transit', async () => { const h = harness({ devicePath: '' }) const res = await processDelete('s', 'alice/a.pdf', h.env, h.log, () => 1) expect(res.status).toBe(200) expect(h.state.objectDeleted).toBe(true) expect(h.state.rowDeleted).toBe(true) expect(h.state.marked).toBe(false) }) it('marks a moved file for withdrawal instead of touching R2', async () => { // The bytes are on the device and the R2 object is already gone. Deleting // here would report success while the client's file stayed on the device // forever — the exact failure this branch exists to prevent. const h = harness({ devicePath: 'jobs/a.pdf' }) const res = await processDelete('s', 'alice/jobs/a.pdf', h.env, h.log, () => 1) expect(res.status).toBe(200) expect(h.state.marked).toBe(true) expect(h.state.rowDeleted).toBe(false) expect(h.state.objectDeleted).toBe(false) expect(h.lines.some((l) => l.includes('op=withdraw') && l.includes('result=marked'))).toBe(true) }) it('tells the client the withdrawal is pending, not complete', async () => { const h = harness({ devicePath: 'jobs/a.pdf' }) const res = await processDelete('s', 'alice/jobs/a.pdf', h.env, h.log, () => 1) expect(res.payload).toMatchObject({ ok: true, pending: true }) }) it('reports an unknown key as not found without writing anything', async () => { const h = harness(null) const res = await processDelete('s', 'alice/nope.pdf', h.env, h.log, () => 1) expect(res.status).toBe(404) expect(h.state.objectDeleted).toBe(false) expect(h.state.marked).toBe(false) expect(h.state.rowDeleted).toBe(false) }) it('still refuses another person key before reading anything', async () => { const h = harness({ devicePath: '' }) const res = await processDelete('s', 'bob/a.pdf', h.env, h.log, () => 1) expect(res.status).toBe(403) expect(h.state.objectDeleted).toBe(false) }) })