mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-17 14:21:25 +02:00
A browser-based roguelike synchronized with crypto block timing. A run is a race against the next block: reach the exit before the block lands, or the house wins. The same codebase runs the solo dungeon, the social Tavern, and operator-selected PvP and race rulesets, with separate free and paid leaderboards. Self-hostable under the MIT License. See README.md to run it, docs/ARCHITECTURE.md for the design, and docs/DEPLOY.md to operate an instance.
81 lines
3.8 KiB
JavaScript
81 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const MIGRATIONS = path.join(__dirname, '../src/migrations');
|
|
const AUDIT = path.join(__dirname, '../scripts/deploy/financial-constraint-audit.sql');
|
|
const VALIDATE = path.join(__dirname, '../scripts/deploy/financial-constraint-validate-restore.sql');
|
|
|
|
const CONSTRAINTS = Object.freeze([
|
|
['payments', 'payments_status_check'],
|
|
['payments', 'payments_payment_type_check'],
|
|
['payouts', 'payouts_status_check'],
|
|
['games', 'games_payout_commitment_complete'],
|
|
['games', 'games_layout_fingerprints_array_check'],
|
|
['race_entry_lots', 'race_entry_lots_remaining_within_original'],
|
|
['match_queue_entries', 'match_queue_entries_status_check'],
|
|
['match_queue_entries', 'match_queue_entries_escrow_nonnegative'],
|
|
['match_queue_entries', 'match_queue_committed_escrow_complete'],
|
|
['matches', 'matches_payout_liability_amount_nonnegative'],
|
|
['matches', 'matches_payout_liability_cap_positive'],
|
|
['matches', 'matches_payout_liability_complete'],
|
|
['matches', 'matches_payout_liability_economics_consistent'],
|
|
['race_entry_lots', 'race_entry_lots_refund_shape'],
|
|
['payment_refunds', 'payment_refunds_progress_nonnegative'],
|
|
['games', 'games_entry_consumption_shape'],
|
|
['payments', 'payments_fairness_binding_complete'],
|
|
['payouts', 'payouts_no_address_review_shape']
|
|
]);
|
|
|
|
function migrationNotValidConstraintNames() {
|
|
const names = new Set();
|
|
for (const fileName of fs.readdirSync(MIGRATIONS).filter(name => name.endsWith('.sql'))) {
|
|
const sql = fs.readFileSync(path.join(MIGRATIONS, fileName), 'utf8');
|
|
for (const marker of sql.matchAll(/NOT\s+VALID\s*;/gi)) {
|
|
const prefix = sql.slice(0, marker.index);
|
|
const declarations = [...prefix.matchAll(/ADD\s+CONSTRAINT\s+([a-z0-9_]+)/gi)];
|
|
if (declarations.length) names.add(declarations.at(-1)[1].toLowerCase());
|
|
}
|
|
}
|
|
return [...names].sort();
|
|
}
|
|
|
|
describe('historical financial constraint release gates', () => {
|
|
const expectedNames = CONSTRAINTS.map(([, name]) => name).sort();
|
|
|
|
test('inventory stays synchronized with every migration NOT VALID constraint', () => {
|
|
expect(migrationNotValidConstraintNames()).toEqual(expectedNames);
|
|
expect(CONSTRAINTS).toHaveLength(18);
|
|
});
|
|
|
|
test('read-only audit names every constraint and checks historical predicates', () => {
|
|
const sql = fs.readFileSync(AUDIT, 'utf8');
|
|
expect(sql).toMatch(/BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY;/i);
|
|
expect(sql).toContain("current_database() = :'expected_database'");
|
|
expect(sql).toContain('historical_constraints_clean');
|
|
expect(sql).toContain('IS FALSE');
|
|
expect(sql).not.toMatch(/\bALTER\s+TABLE\b/i);
|
|
expect(sql).not.toMatch(/\b(?:INSERT\s+INTO|UPDATE|DELETE\s+FROM|TRUNCATE)\b/i);
|
|
for (const [, name] of CONSTRAINTS) {
|
|
expect(sql).toContain(`'${name}'`);
|
|
}
|
|
});
|
|
|
|
test('disposable-restore gate explicitly validates every constraint then rolls back', () => {
|
|
const sql = fs.readFileSync(VALIDATE, 'utf8');
|
|
expect(sql).toContain("current_database() ~* '(restore|scratch)'");
|
|
expect(sql).toContain('I_CONFIRM_THIS_DATABASE_IS_A_DISPOSABLE_RESTORE');
|
|
expect(sql).toContain('\\ir financial-constraint-audit.sql');
|
|
expect(sql).toContain('SET LOCAL lock_timeout');
|
|
expect(sql).toMatch(/ROLLBACK;[\s\S]*PASS: PostgreSQL validated all 18 constraints/i);
|
|
for (const [table, name] of CONSTRAINTS) {
|
|
expect(sql).toContain(
|
|
`ALTER TABLE public.${table} VALIDATE CONSTRAINT ${name};`
|
|
);
|
|
}
|
|
expect((sql.match(/\bVALIDATE CONSTRAINT\s+[a-z0-9_]+;/gi) || []))
|
|
.toHaveLength(CONSTRAINTS.length);
|
|
});
|
|
});
|