import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import express from 'express'; import { initDb } from '../src/db.js'; import { createStatsRouter } from '../src/routes/stats.js'; /** * 테스트용 카드 데이터 파일을 생성한다. * @param {string} datasPath * @returns {void} */ function writeTestDatas(datasPath) { fs.mkdirSync(datasPath, { recursive: true }); fs.writeFileSync( path.join(datasPath, 'ua01.js'), ` export const cardList = [ { imgSrc: '/ua01/001.png', Number: 'UA01-001', parallel: false }, { imgSrc: '/ua01/001_p1.png', Number: 'UA01-001', parallel: true }, { imgSrc: '/ua01/002.png', Number: 'UA01-002', parallel: false }, ]; `, ); fs.writeFileSync( path.join(datasPath, 'uapr.js'), ` export const cardList = [ { imgSrc: '/uapr/001.png', Number: 'UAPR-001', parallel: false }, ]; `, ); fs.writeFileSync( path.join(datasPath, 'ua99 sample.js'), ` export const cardList = [ { imgSrc: '/sample/001.png', Number: 'SAMPLE-001', parallel: false }, ]; `, ); } /** * 테스트 중 바꾼 환경변수를 원래 상태로 되돌린다. * @param {string} key * @param {string | undefined} value * @returns {void} */ function restoreEnv(key, value) { if (value === undefined) { delete process.env[key]; } else { process.env[key] = value; } } test('통계 API가 DB와 카드 데이터 운영 지표를 반환한다', async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'uniare-stats-')); const dbPath = path.join(tempDir, 'test.db'); const datasPath = path.join(tempDir, 'datas'); const previousDatasPath = process.env.DATAS_PATH; const previousToken = process.env.STATS_API_TOKEN; process.env.DATAS_PATH = datasPath; delete process.env.STATS_API_TOKEN; writeTestDatas(datasPath); const db = initDb(dbPath); db.prepare(` INSERT INTO users ( email, username, password_hash, display_name, email_verified_at, last_login_at ) VALUES ('active@example.com', 'active', 'hash', 'active', datetime('now'), datetime('now')), ('pending@example.com', 'pending', 'hash', 'pending', NULL, NULL) `).run(); db.prepare(` INSERT INTO decks ( share_code, share_type, user_id, series, series_name, name, cards_json, is_public, view_count ) VALUES ('PUBLIC01', 'user', 1, 'ua01', '테스트', '공개 덱', '[]', 1, 3), ('PRIVATE1', 'user', 1, 'ua01', '테스트', '비공개 덱', '[]', 0, 2), ('PRESET01', 'preset', NULL, 'uapr', '프로모', '프리셋', '[]', 1, 5) `).run(); const app = express(); app.use('/api/stats', createStatsRouter(db, '4.0.test')); const server = app.listen(0); try { await new Promise((resolve) => server.once('listening', resolve)); const address = server.address(); const response = await fetch(`http://127.0.0.1:${address.port}/api/stats`); assert.equal(response.status, 200); const body = await response.json(); assert.equal(body.status, 'online'); assert.equal(body.version, '4.0.test'); assert.equal(body.members.total, 2); assert.equal(body.members.verified, 1); assert.equal(body.members.unverified, 1); assert.equal(body.decks.total, 3); assert.equal(body.decks.publicTotal, 2); assert.equal(body.decks.privateTotal, 1); assert.equal(body.decks.totalViews, 10); assert.equal(body.cards.seriesTotal, 2); assert.equal(body.cards.cardTypesTotal, 3); assert.equal(body.cards.cardPrintsTotal, 4); assert.equal(body.cards.parallelPrintsTotal, 1); } finally { server.close(); db.close(); restoreEnv('DATAS_PATH', previousDatasPath); restoreEnv('STATS_API_TOKEN', previousToken); fs.rmSync(tempDir, { recursive: true, force: true }); } }); test('통계 API 토큰이 설정되면 인증 헤더를 요구한다', async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'uniare-stats-token-')); const dbPath = path.join(tempDir, 'test.db'); const datasPath = path.join(tempDir, 'datas'); const previousDatasPath = process.env.DATAS_PATH; const previousToken = process.env.STATS_API_TOKEN; process.env.DATAS_PATH = datasPath; process.env.STATS_API_TOKEN = 'dashboard-secret'; writeTestDatas(datasPath); const db = initDb(dbPath); const app = express(); app.use('/api/stats', createStatsRouter(db, '4.0.test')); const server = app.listen(0); try { await new Promise((resolve) => server.once('listening', resolve)); const address = server.address(); const baseUrl = `http://127.0.0.1:${address.port}/api/stats`; const denied = await fetch(baseUrl); assert.equal(denied.status, 401); const allowed = await fetch(baseUrl, { headers: { Authorization: 'Bearer dashboard-secret' }, }); assert.equal(allowed.status, 200); } finally { server.close(); db.close(); restoreEnv('DATAS_PATH', previousDatasPath); restoreEnv('STATS_API_TOKEN', previousToken); fs.rmSync(tempDir, { recursive: true, force: true }); } });