Files
uniare.sori.studio/api/test/stats.test.js
2026-08-12 17:57:35 +09:00

156 lines
5.3 KiB
JavaScript

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 });
}
});