import { describe, it, expect } from 'vitest' import { buildTestApp, setupAdmin, auth } from './helpers.js' import { discogsReleaseFixture } from './fixtures.js' const discogsStub = (async (input: any) => String(input).includes('/releases/1001') ? new Response(JSON.stringify(discogsReleaseFixture), { status: 200, headers: { 'content-type': 'application/json' }, }) : new Response('nope', { status: 404 })) as typeof fetch async function appWithItem() { const app = await buildTestApp(discogsStub) const cookie = await setupAdmin(app) await app.inject({ method: 'PUT', url: '/api/settings', ...auth(cookie), payload: { discogsToken: 't' } }) const added = await app.inject({ method: 'POST', url: '/api/collection', ...auth(cookie), payload: { releaseId: 1001 } }) return { app, cookie, itemId: added.json().id as number } } describe('loans', () => { it('lend, list active, return', async () => { const { app, cookie, itemId } = await appWithItem() const lend = await app.inject({ method: 'POST', url: `/api/collection/${itemId}/loan`, ...auth(cookie), payload: { borrower: 'Bob' }, }) expect(lend.statusCode).toBe(200) const loan = lend.json() expect(loan).toMatchObject({ itemId, borrower: 'Bob', returnedAt: null }) const dup = await app.inject({ method: 'POST', url: `/api/collection/${itemId}/loan`, ...auth(cookie), payload: { borrower: 'Eve' }, }) expect(dup.statusCode).toBe(409) expect(dup.json()).toEqual({ error: 'already_on_loan' }) const list = await app.inject({ method: 'GET', url: '/api/loans', ...auth(cookie) }) expect(list.json().active).toHaveLength(1) expect(list.json().history).toHaveLength(0) const ret = await app.inject({ method: 'POST', url: `/api/loans/${loan.id}/return`, ...auth(cookie) }) expect(ret.statusCode).toBe(200) const after = await app.inject({ method: 'GET', url: '/api/loans', ...auth(cookie) }) expect(after.json().active).toHaveLength(0) expect(after.json().history).toHaveLength(1) const again = await app.inject({ method: 'POST', url: `/api/loans/${loan.id}/return`, ...auth(cookie) }) expect(again.statusCode).toBe(404) await app.close() }) it('detail route reports the active loan, null before lending', async () => { const { app, cookie, itemId } = await appWithItem() const before = await app.inject({ method: 'GET', url: `/api/collection/${itemId}`, ...auth(cookie) }) expect(before.json().loan).toBeNull() await app.inject({ method: 'POST', url: `/api/collection/${itemId}/loan`, ...auth(cookie), payload: { borrower: 'Bob' }, }) const after = await app.inject({ method: 'GET', url: `/api/collection/${itemId}`, ...auth(cookie) }) expect(after.json().loan).toMatchObject({ borrower: 'Bob' }) await app.close() }) it('validates borrower and ownership', async () => { const { app, cookie, itemId } = await appWithItem() const empty = await app.inject({ method: 'POST', url: `/api/collection/${itemId}/loan`, ...auth(cookie), payload: { borrower: ' ' }, }) expect(empty.statusCode).toBe(400) const missing = await app.inject({ method: 'POST', url: `/api/collection/9999/loan`, ...auth(cookie), payload: { borrower: 'Bob' }, }) expect(missing.statusCode).toBe(404) await app.close() }) })