48 lines
2.5 KiB
TypeScript
48 lines
2.5 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest'
|
||
|
|
import { openDatabase } from '../src/db.js'
|
||
|
|
import { buildTestAppWithDb, setupAdmin, auth } from './helpers.js'
|
||
|
|
|
||
|
|
function seedItems(db: ReturnType<typeof openDatabase>) {
|
||
|
|
const ins = db.prepare(
|
||
|
|
`INSERT INTO collection_items (user_id, discogs_release_id, title, artist, year, formats, genres, date_added, rip_override)
|
||
|
|
VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||
|
|
)
|
||
|
|
ins.run(1, 'Motion', 'The Cinematic Orchestra', 1999, JSON.stringify(['CD']), JSON.stringify(['Electronic']), '2026-08-01 10:00:00', null)
|
||
|
|
ins.run(2, 'Blue Lines', 'Massive Attack', 1991, JSON.stringify(['Vinyl']), JSON.stringify(['Electronic']), '2026-08-15 10:00:00', 1)
|
||
|
|
ins.run(3, 'Mezzanine', 'Massive Attack', 1998, JSON.stringify(['CD']), JSON.stringify(['Downtempo', 'Electronic']), '2026-08-20 10:00:00', null)
|
||
|
|
ins.run(4, 'Dummy', 'Portishead', 1994, JSON.stringify(['CD', 'Album']), JSON.stringify(['Downtempo']), '2025-09-01 10:00:00', null)
|
||
|
|
db.prepare("INSERT INTO loans (user_id, item_id, borrower) VALUES (1, 2, 'Bob')").run()
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('GET /api/stats', () => {
|
||
|
|
it('aggregates totals, formats, genres, artists, months, ratio, loans', async () => {
|
||
|
|
const db = openDatabase(':memory:')
|
||
|
|
const app = await buildTestAppWithDb(db)
|
||
|
|
const cookie = await setupAdmin(app)
|
||
|
|
// seed after setup so user_id 1 (admin) exists and FKs hold
|
||
|
|
seedItems(db)
|
||
|
|
const res = await app.inject({ method: 'GET', url: '/api/stats', ...auth(cookie) })
|
||
|
|
expect(res.statusCode).toBe(200)
|
||
|
|
const s = res.json()
|
||
|
|
// only Blue Lines has rip_override=1; no digital albums/match_links → ripped=1, notRipped=3
|
||
|
|
expect(s.totals).toEqual({ items: 4, ripped: 1, notRipped: 3, onLoan: 1 })
|
||
|
|
expect(s.ripRatio).toBeCloseTo(0.25)
|
||
|
|
expect(s.formats).toEqual([
|
||
|
|
{ name: 'CD', count: 3 },
|
||
|
|
{ name: 'Vinyl', count: 1 },
|
||
|
|
{ name: 'Album', count: 1 },
|
||
|
|
])
|
||
|
|
expect(s.topGenres.slice(0, 2)).toEqual([
|
||
|
|
{ name: 'Electronic', count: 3 },
|
||
|
|
{ name: 'Downtempo', count: 2 },
|
||
|
|
])
|
||
|
|
// Massive Attack (2) leads; Cinematic Orchestra and Portishead both 1 → assert lead + total only
|
||
|
|
expect(s.topArtists).toHaveLength(3)
|
||
|
|
expect(s.topArtists[0]).toEqual({ name: 'Massive Attack', count: 2 })
|
||
|
|
// 2026-08 items: Motion, Blue Lines, Mezzanine (Dummy 2025-09 is outside the 12-month window)
|
||
|
|
const aug = s.addedByMonth.find((m: { month: string }) => m.month === '2026-08')
|
||
|
|
expect(aug).toEqual({ month: '2026-08', count: 3 })
|
||
|
|
await app.close()
|
||
|
|
})
|
||
|
|
})
|