Files
filezzy-staging/backend/scripts/test-all-tiers-config-api.ts
2026-02-04 14:16:04 +01:00

192 lines
8.1 KiB
TypeScript

#!/usr/bin/env ts-node
/**
* Test: All tiers (Guest, Free, Day Pass, Pro) API vs runtime config (DB).
* Compares GET /api/v1/user/limits responses with GET /api/v1/config for each tier.
* Run: npx ts-node scripts/test-all-tiers-config-api.ts
* API_URL=http://127.0.0.1:4000 npx ts-node scripts/test-all-tiers-config-api.ts
* Backend: set ALLOW_TEST_JWT=1 (or NODE_ENV=test) to accept test tokens. Run db:seed-test-users first.
* Day Pass: optional; script auto-uses test-daypass-user-001 if seeded.
*/
import axios from 'axios';
import jwt from 'jsonwebtoken';
const BASE_URL = process.env.API_URL || 'http://127.0.0.1:4000';
const JWT_SECRET = process.env.JWT_SECRET || 'test-secret';
type PublicConfig = Record<string, unknown>;
interface LimitsResponse {
tier: string;
limits: { maxFileSizeMb: number; maxFilesPerBatch: number; maxBatchSizeMb: number };
opsLimit: number | null;
opsUsedToday: number | null;
nextReset: string | null;
}
function assert(condition: boolean, message: string): void {
if (!condition) throw new Error(message);
}
function getConfigNumber(config: PublicConfig, key: string): number {
const v = Number(config[key]);
assert(!Number.isNaN(v), `${key} missing or invalid in config`);
return v;
}
function compareTierLimits(
config: PublicConfig,
tierKey: 'guest' | 'free' | 'daypass' | 'pro',
data: LimitsResponse,
opsKey: 'max_ops_per_day_guest' | 'max_ops_per_day_free' | 'max_ops_per_24h_daypass'
): void {
const maxFileMb = getConfigNumber(config, `max_file_size_mb_${tierKey}`);
const maxFilesBatch = getConfigNumber(config, `max_files_per_batch_${tierKey}`);
const maxBatchMb = getConfigNumber(config, `max_batch_size_mb_${tierKey}`);
assert(data.limits.maxFileSizeMb === maxFileMb, `maxFileSizeMb: API=${data.limits.maxFileSizeMb}, config=${maxFileMb}`);
assert(data.limits.maxFilesPerBatch === maxFilesBatch, `maxFilesPerBatch: API=${data.limits.maxFilesPerBatch}, config=${maxFilesBatch}`);
assert(data.limits.maxBatchSizeMb === maxBatchMb, `maxBatchSizeMb: API=${data.limits.maxBatchSizeMb}, config=${maxBatchMb}`);
if (tierKey === 'pro') {
assert(data.opsLimit === null, `Pro tier opsLimit should be null, got ${data.opsLimit}`);
} else {
const expectedOps = getConfigNumber(config, opsKey);
assert(data.opsLimit === expectedOps, `opsLimit: API=${data.opsLimit}, config=${expectedOps}`);
}
}
async function main(): Promise<void> {
console.log('\n=== All Tiers API vs Runtime Config (DB) ===\n');
console.log('Base URL:', BASE_URL);
const configRes = await axios.get<PublicConfig>(`${BASE_URL}/api/v1/config`, {
validateStatus: () => true,
timeout: 10000,
});
assert(configRes.status === 200, `GET /api/v1/config failed: ${configRes.status}`);
const config = configRes.data;
console.log(' GET /api/v1/config: 200 OK');
// Print configured values (runtime config from DB)
console.log('\n Configured tier limits (from GET /api/v1/config):');
for (const t of ['guest', 'free', 'daypass', 'pro'] as const) {
const fileMb = config[`max_file_size_mb_${t}`];
const filesBatch = config[`max_files_per_batch_${t}`];
const batchMb = config[`max_batch_size_mb_${t}`];
const opsKey = t === 'daypass' ? 'max_ops_per_24h_daypass' : t === 'pro' ? null : `max_ops_per_day_${t}`;
const ops = opsKey ? config[opsKey] : null;
console.log(` ${t}: maxFileSizeMb=${fileMb}, maxFilesPerBatch=${filesBatch}, maxBatchSizeMb=${batchMb}, opsLimit=${ops ?? 'n/a'}`);
}
console.log('');
// --- Guest ---
console.log('--- GUEST (no auth) ---');
const guestRes = await axios.get<LimitsResponse>(`${BASE_URL}/api/v1/user/limits`, {
validateStatus: () => true,
timeout: 10000,
});
assert(guestRes.status === 200, `GET /api/v1/user/limits (guest) failed: ${guestRes.status}`);
const guest = guestRes.data;
assert(guest.tier === 'GUEST', `Expected tier GUEST, got ${guest.tier}`);
compareTierLimits(config, 'guest', guest, 'max_ops_per_day_guest');
console.log(` tier: GUEST`);
console.log(` limits: maxFileSizeMb=${guest.limits.maxFileSizeMb}, maxFilesPerBatch=${guest.limits.maxFilesPerBatch}, maxBatchSizeMb=${guest.limits.maxBatchSizeMb}`);
console.log(` opsLimit: ${guest.opsLimit} (matches config)\n`);
// --- Free ---
console.log('--- FREE (Bearer free token) ---');
const freeToken = jwt.sign(
{ sub: 'test-free-user-001', email: 'free-user@test.com', preferred_username: 'freeuser', realm_access: { roles: [] } },
JWT_SECRET,
{ expiresIn: '24h' }
);
const freeRes = await axios.get<LimitsResponse>(`${BASE_URL}/api/v1/user/limits`, {
headers: { Authorization: `Bearer ${freeToken}` },
validateStatus: () => true,
timeout: 10000,
});
assert(freeRes.status === 200, `GET /api/v1/user/limits (free) failed: ${freeRes.status}`);
const free = freeRes.data;
assert(
free.tier === 'FREE',
free.tier === 'GUEST'
? 'Expected tier FREE, got GUEST (backend needs ALLOW_TEST_JWT=1 or NODE_ENV=test to accept test tokens; run db:seed-test-users)'
: `Expected tier FREE, got ${free.tier}`
);
compareTierLimits(config, 'free', free, 'max_ops_per_day_free');
console.log(` tier: FREE`);
console.log(` limits: maxFileSizeMb=${free.limits.maxFileSizeMb}, maxFilesPerBatch=${free.limits.maxFilesPerBatch}, maxBatchSizeMb=${free.limits.maxBatchSizeMb}`);
console.log(` opsLimit: ${free.opsLimit} (matches config)\n`);
// --- Pro ---
console.log('--- PRO (Bearer premium token) ---');
const proToken = jwt.sign(
{
sub: 'test-premium-user-001',
email: 'premium-user@test.com',
preferred_username: 'premiumuser',
realm_access: { roles: ['premium-user'] },
},
JWT_SECRET,
{ expiresIn: '24h' }
);
const proRes = await axios.get<LimitsResponse>(`${BASE_URL}/api/v1/user/limits`, {
headers: { Authorization: `Bearer ${proToken}` },
validateStatus: () => true,
timeout: 10000,
});
assert(proRes.status === 200, `GET /api/v1/user/limits (pro) failed: ${proRes.status}`);
const pro = proRes.data;
assert(
pro.tier === 'PRO',
pro.tier === 'GUEST'
? 'Expected tier PRO, got GUEST (backend needs ALLOW_TEST_JWT=1 or NODE_ENV=test; run db:seed-test-users)'
: `Expected tier PRO, got ${pro.tier}`
);
compareTierLimits(config, 'pro', pro, 'max_ops_per_day_free'); // ops not used for pro
console.log(` tier: PRO`);
console.log(` limits: maxFileSizeMb=${pro.limits.maxFileSizeMb}, maxFilesPerBatch=${pro.limits.maxFilesPerBatch}, maxBatchSizeMb=${pro.limits.maxBatchSizeMb}`);
console.log(` opsLimit: ${pro.opsLimit} (null for Pro)\n`);
// --- Day Pass (optional: DAY_PASS_TOKEN or auto token for test-daypass-user-001 after seed-test-users-for-api) ---
let dayPassToken = process.env.DAY_PASS_TOKEN?.trim();
if (!dayPassToken) {
const dayPassAutoToken = jwt.sign(
{
sub: 'test-daypass-user-001',
email: 'daypass-user@test.com',
preferred_username: 'daypassuser',
realm_access: { roles: [] },
},
JWT_SECRET,
{ expiresIn: '24h' }
);
dayPassToken = `Bearer ${dayPassAutoToken}`;
} else {
dayPassToken = dayPassToken.startsWith('Bearer ') ? dayPassToken : `Bearer ${dayPassToken}`;
}
console.log('--- DAY_PASS ---');
const dayPassRes = await axios.get<LimitsResponse>(`${BASE_URL}/api/v1/user/limits`, {
headers: { Authorization: dayPassToken },
validateStatus: () => true,
timeout: 10000,
});
if (dayPassRes.status === 200 && dayPassRes.data.tier === 'DAY_PASS') {
const dayPass = dayPassRes.data;
compareTierLimits(config, 'daypass', dayPass, 'max_ops_per_24h_daypass');
console.log(` tier: DAY_PASS`);
console.log(` limits: maxFileSizeMb=${dayPass.limits.maxFileSizeMb}, maxFilesPerBatch=${dayPass.limits.maxFilesPerBatch}, maxBatchSizeMb=${dayPass.limits.maxBatchSizeMb}`);
console.log(` opsLimit: ${dayPass.opsLimit} (matches config)\n`);
} else {
console.log(` skipped (backend returned tier=${dayPassRes.data?.tier ?? '?'}; run db:seed-test-users then ALLOW_TEST_JWT=1)\n`);
}
console.log('=== All tier vs config comparisons passed ===\n');
}
main().catch((err) => {
console.error('\nFAIL:', err.message);
process.exit(1);
});