108 lines
5.3 KiB
TypeScript
108 lines
5.3 KiB
TypeScript
process.env.DATABASE_PATH = ':memory:';
|
|
process.env.THEMES_DIR = './themes';
|
|
process.env.FRONTEND_DIST = './frontend/dist';
|
|
process.env.NODE_ENV = 'test';
|
|
|
|
import { Test } from '@nestjs/testing';
|
|
import { TypeOrmModule, getDataSourceToken } from '@nestjs/typeorm';
|
|
import { ConfigModule } from '@nestjs/config';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { validateEnv } from '../../backend/src/config/env.schema';
|
|
import { BackupService } from '../../backend/src/modules/admin/system/backup.service';
|
|
import { UserEntity } from '../../backend/src/database/entities/user.entity';
|
|
import { SettingEntity } from '../../backend/src/database/entities/setting.entity';
|
|
import { CategoryEntity } from '../../backend/src/database/entities/category.entity';
|
|
import { ChallengeEntity } from '../../backend/src/database/entities/challenge.entity';
|
|
import { ChallengeFileEntity } from '../../backend/src/database/entities/challenge-file.entity';
|
|
import { SolveEntity } from '../../backend/src/database/entities/solve.entity';
|
|
import { RefreshTokenEntity } from '../../backend/src/database/entities/refresh-token.entity';
|
|
import { BlogPostEntity } from '../../backend/src/database/entities/blog-post.entity';
|
|
import { AdminOperationTokenEntity } from '../../backend/src/database/entities/admin-operation-token.entity';
|
|
import { InitSchema1700000000000 } from '../../backend/src/database/migrations/1700000000000-InitSchema';
|
|
import { SeedSystemData1700000000100 } from '../../backend/src/database/migrations/1700000000100-SeedSystemData';
|
|
import { AddCategoryTimestampsAndUniqueAbbrev1700000000200 } from '../../backend/src/database/migrations/1700000000200-AddCategoryTimestampsAndUniqueAbbrev';
|
|
import { UpdateSystemCategoryKeys1700000000300 } from '../../backend/src/database/migrations/1700000000300-UpdateSystemCategoryKeys';
|
|
import { RepairCategorySchemaAndSystemCategories1700000000400 } from '../../backend/src/database/migrations/1700000000400-RepairCategorySchemaAndSystemCategories';
|
|
import { UpgradeChallengeAdminSchema1700000000500 } from '../../backend/src/database/migrations/1700000000500-UpgradeChallengeAdminSchema';
|
|
import { AddAdminOperationTokens1700000000900 } from '../../backend/src/database/migrations/1700000000900-AddAdminOperationTokens';
|
|
|
|
describe('Job 871: BackupService captures + filters', () => {
|
|
let moduleRef: any;
|
|
let backup: BackupService;
|
|
let uploadDir: string;
|
|
|
|
beforeAll(async () => {
|
|
uploadDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hipctf-sys-backup-'));
|
|
process.env.UPLOAD_DIR = uploadDir;
|
|
fs.writeFileSync(path.join(uploadDir, 'logo.bin'), Buffer.from('LOGO-BYTES'));
|
|
fs.mkdirSync(path.join(uploadDir, 'icons'), { recursive: true });
|
|
fs.writeFileSync(path.join(uploadDir, 'icons', 'icon.png'), Buffer.from('ICON'));
|
|
|
|
moduleRef = await Test.createTestingModule({
|
|
imports: [
|
|
ConfigModule.forRoot({ isGlobal: true, validate: validateEnv }),
|
|
TypeOrmModule.forRoot({
|
|
type: 'better-sqlite3',
|
|
database: ':memory:',
|
|
entities: [
|
|
UserEntity, SettingEntity, CategoryEntity, ChallengeEntity,
|
|
ChallengeFileEntity, SolveEntity, RefreshTokenEntity, BlogPostEntity,
|
|
AdminOperationTokenEntity,
|
|
],
|
|
migrations: [
|
|
InitSchema1700000000000,
|
|
SeedSystemData1700000000100,
|
|
AddCategoryTimestampsAndUniqueAbbrev1700000000200,
|
|
UpdateSystemCategoryKeys1700000000300,
|
|
RepairCategorySchemaAndSystemCategories1700000000400,
|
|
UpgradeChallengeAdminSchema1700000000500,
|
|
AddAdminOperationTokens1700000000900,
|
|
],
|
|
migrationsRun: true,
|
|
synchronize: false,
|
|
}),
|
|
],
|
|
providers: [BackupService],
|
|
}).compile();
|
|
backup = moduleRef.get(BackupService);
|
|
const dataSource = moduleRef.get(getDataSourceToken());
|
|
await dataSource.query(
|
|
`INSERT INTO user (id,username,password_hash,role,status,created_at) VALUES (?,?,?,?,?,strftime('%Y-%m-%dT%H:%M:%fZ','now'))`,
|
|
['u-1', 'alice', 'HASH', 'player', 'enabled'],
|
|
);
|
|
await dataSource.query(`INSERT INTO setting (key,value) VALUES (?,?)`, ['testCustomKey', 'HIPCTF']);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await moduleRef?.close();
|
|
if (uploadDir && fs.existsSync(uploadDir)) {
|
|
try {
|
|
fs.rmSync(uploadDir, { recursive: true, force: true });
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|
|
});
|
|
|
|
it('captures every application table and base64-encoded uploads', async () => {
|
|
const obj = await backup.build();
|
|
expect(obj.format).toBe('hipctf-system-backup');
|
|
expect(obj.tables.user.length).toBeGreaterThanOrEqual(1);
|
|
expect(obj.tables.setting.length).toBeGreaterThanOrEqual(1);
|
|
expect(obj.uploads.length).toBeGreaterThanOrEqual(1);
|
|
const logo = obj.uploads.find((u) => u.path.includes('logo.bin'));
|
|
expect(logo).toBeDefined();
|
|
expect(Buffer.from(logo!.dataBase64, 'base64').toString()).toBe('LOGO-BYTES');
|
|
});
|
|
|
|
it('never includes operational or sqlite-internal tables', async () => {
|
|
const obj = await backup.build();
|
|
expect(Object.keys(obj.tables)).not.toContain('admin_operation_token');
|
|
expect(Object.keys(obj.tables)).not.toContain('migrations');
|
|
expect(Object.keys(obj.tables)).not.toContain('sqlite_master');
|
|
expect(obj.tableCounts['admin_operation_token']).toBeUndefined();
|
|
});
|
|
});
|