import test from 'brittle'
import {
DEDUP_RECLAIM_AUTH_MESSAGE,
parseDedupReclaimOptions,
resolveDedupReclaimRoute,
runDedupReclaimAction
} from '../../packages/core/core/relay-node/api-dedup-reclaim.js'
test('api dedup reclaim: route helper maps exact operator reclaim route', (t) => {
t.alike(resolveDedupReclaimRoute('POST', '/api/dedup/reclaim'), {
kind: 'dedup-reclaim',
authMessage: DEDUP_RECLAIM_AUTH_MESSAGE
})
t.is(resolveDedupReclaimRoute('GET', '/api/dedup/reclaim'), null)
t.is(resolveDedupReclaimRoute('POST', '/api/dedup'), null)
t.is(resolveDedupReclaimRoute('POST', '/api/dedup/reclaim/all'), null)
})
test('api dedup reclaim: defaults to dry-run with zero retained superseded versions', async (t) => {
const calls = []
const node = {
eviction: {
async reclaimSuperseded (opts) {
calls.push(opts)
return { dryRun: opts.dryRun, reclaimed: [], skipped: [], freedBytes: 0, candidates: 0 }
}
}
}
const out = await runDedupReclaimAction({ body: {}, node })
t.is(out.status, 200)
t.is(out.payload.ok, true)
t.is(out.payload.dryRun, true)
t.alike(calls, [{ dryRun: true, retainVersions: 0, max: undefined }])
})
test('api dedup reclaim: parses execute, retainVersions, and max without numeric coercion', async (t) => {
const calls = []
const node = {
eviction: {
async reclaimSuperseded (opts) {
calls.push(opts)
return { dryRun: opts.dryRun, reclaimed: [{ appKey: 'a'.repeat(64), bytes: 7 }], freedBytes: 7, candidates: 1 }
}
}
}
const out = await runDedupReclaimAction({
body: { execute: true, retainVersions: 2, max: 3 },
node
})
t.is(out.status, 200)
t.is(out.payload.ok, true)
t.is(out.payload.dryRun, false)
t.is(out.payload.freedBytes, 7)
t.alike(calls, [{ dryRun: false, retainVersions: 2, max: 3 }])
const parsed = parseDedupReclaimOptions({ execute: 'true', retainVersions: 0 })
t.ok(parsed.ok)
t.is(parsed.options.dryRun, true, 'only boolean true executes destructive reclaim')
})
test('api dedup reclaim: rejects malformed body and integer options before reclaiming', async (t) => {
const node = {
eviction: {
async reclaimSuperseded () {
t.fail('invalid requests must not reach eviction manager')
}
}
}
const cases = [
[null, 'JSON body object required'],
[[], 'JSON body object required'],
[{ retainVersions: -1 }, 'retainVersions must be a non-negative integer'],
[{ retainVersions: 1.5 }, 'retainVersions must be a non-negative integer'],
[{ retainVersions: '1' }, 'retainVersions must be a non-negative integer'],
[{ max: 0 }, 'max must be a positive integer'],
[{ max: 2.5 }, 'max must be a positive integer'],
[{ max: '2' }, 'max must be a positive integer'],
[{ max: Number.MAX_SAFE_INTEGER + 1 }, 'max must be a positive integer']
]
for (const [body, message] of cases) {
const out = await runDedupReclaimAction({ body, node })
t.is(out.status, 400)
t.ok(out.payload.error.includes(message), message)
}
})
test('api dedup reclaim: reports unavailable eviction manager', async (t) => {
const out = await runDedupReclaimAction({
body: {},
node: { eviction: {} }
})
t.is(out.status, 503)
t.is(out.payload.error, 'dedup reclaim not available (eviction manager not enabled)')
})
test('api dedup reclaim: redacts unexpected reclaim failures and emits raw error internally', async (t) => {
const error = new Error('filesystem path /data/private/core leaked')
const events = []
const node = {
eviction: {
async reclaimSuperseded () {
throw error
}
}
}
const out = await runDedupReclaimAction({
body: { execute: true },
node,
emit: (...args) => events.push(args)
})
t.is(out.status, 500)
t.ok(out.payload.error.startsWith('reclaim-failed: '))
t.absent(out.payload.error.includes('/data/private'))
t.is(events.length, 1)
t.is(events[0][0], 'dedup-reclaim-error')
t.is(events[0][1].error, error)
})