Files
wownerogue/test/economicImmutability.test.js
jwinterm fad2a695bd Wownerogue
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.
2026-07-27 08:23:01 -04:00

97 lines
5.1 KiB
JavaScript

const fs = require('fs');
const path = require('path');
describe('database economic identity immutability', () => {
const readMigration = name => fs.readFileSync(
path.join(__dirname, `../src/migrations/${name}`), 'utf8'
);
test('committed solo owner is frozen on and after commitment', () => {
const initial = readMigration('032_solo_liability_invariants.sql');
const forward = readMigration('038_economic_identity_immutability.sql');
for (const sql of [initial, forward]) {
expect(sql).toMatch(/NEW\.user_id IS DISTINCT FROM OLD\.user_id/i);
expect(sql).toMatch(/OLD\.payout_committed_at IS NULL[\s\S]*NEW\.payout_committed_at IS NOT NULL[\s\S]*NEW\.user_id IS DISTINCT FROM OLD\.user_id/i);
}
});
test('receipt-backed or final invoice economics, destination, product, and provider are frozen', () => {
const forward = readMigration('038_economic_identity_immutability.sql');
for (const field of [
'user_id', 'payment_type', 'expected_amount', 'subaddress', 'address_index',
'provider_id', 'provider_invoice_id', 'payment_mode', 'credit_package_id',
'product_id', 'product_grants', 'description', 'expires_at'
]) {
expect(forward).toMatch(new RegExp(`NEW\\.${field} IS DISTINCT FROM OLD\\.${field}`, 'i'));
}
expect(forward).toMatch(/OLD\.status IN \('confirmed', 'refunded', 'expired'\)/i);
expect(forward).toMatch(/EXISTS \(SELECT 1 FROM payment_receipts r WHERE r\.payment_id = OLD\.id\)/i);
expect(forward).toMatch(/OLD\.fairness_bound_at IS NULL AND NEW\.fairness_bound_at IS NOT NULL/i);
});
test('confirmed-payment refunds change operational status without rewriting immutable description', () => {
const refundService = fs.readFileSync(
path.join(__dirname, '../src/services/paymentRefundService.js'), 'utf8'
);
const update = refundService.match(/UPDATE payments[\s\S]*?WHERE id = \$1/gi) || [];
expect(update).toHaveLength(1);
expect(update[0]).toMatch(/SET status = 'refunded'/i);
expect(update[0]).not.toMatch(/description/i);
});
test('payment receipt authorization evidence is append-only', () => {
const forward = readMigration('038_economic_identity_immutability.sql');
expect(forward).toMatch(/CREATE TRIGGER trg_payment_receipts_append_only/i);
expect(forward).toMatch(/BEFORE UPDATE OR DELETE ON payment_receipts/i);
expect(forward).toMatch(/payment receipt evidence is append-only/i);
});
test('payout economics are immutable with only the exact saved-address claim exception', () => {
const sql = readMigration('038_economic_identity_immutability.sql');
for (const field of ['user_id', 'game_id', 'match_id', 'amount', 'multiplier', 'reason']) {
expect(sql).toMatch(new RegExp(`NEW\\.${field} IS DISTINCT FROM OLD\\.${field}`, 'i'));
}
expect(sql).toMatch(/OLD\.payout_address = 'PENDING_NO_ADDRESS'/i);
expect(sql).toMatch(/OLD\.status = 'needs_review'/i);
expect(sql).toMatch(/NEW\.status = 'pending'/i);
expect(sql).toMatch(/OLD\.tx_hash IS NULL[\s\S]*NEW\.tx_hash IS NULL/i);
expect(sql).toMatch(/u\.id = OLD\.user_id[\s\S]*u\.payout_address = NEW\.payout_address/i);
expect(sql).toMatch(/CREATE TRIGGER trg_payouts_immutable_obligation/i);
});
test('unresolved solo-win obligations are recordable but excluded from automatic address claim', () => {
const initial = readMigration('038_economic_identity_immutability.sql');
const forward = readMigration('039_unresolved_solo_payout_review.sql');
const runtime = fs.readFileSync(
path.join(__dirname, '../src/game/gameModeManager.js'), 'utf8'
);
expect(runtime).toContain("'solo_winner_identity_review'");
expect(forward).toContain("'solo_winner_identity_review'");
expect(forward).toMatch(/status = 'needs_review'[\s\S]*tx_hash IS NULL/i);
expect(initial).toMatch(
/OLD\.reason IN \('match_winner_no_address', 'solo_winner_no_address'\)/i
);
expect(initial).not.toMatch(
/OLD\.reason IN \([^)]*solo_winner_identity_review/i
);
});
test('paid entropy precommit witness is shaped, immutable, and locks active fairness identity', () => {
const sql = readMigration('040_paid_match_entropy_precommit.sql');
expect(sql).toMatch(/ADD COLUMN IF NOT EXISTS entropy_precommit_tip_height BIGINT/i);
expect(sql).toMatch(/ADD COLUMN IF NOT EXISTS entropy_precommit_verified_at TIMESTAMPTZ/i);
expect(sql).toMatch(/entropy_precommit_tip_height < start_block_height/i);
expect(sql).toMatch(/NEW\.entropy_precommit_tip_height IS DISTINCT FROM OLD\.entropy_precommit_tip_height/i);
expect(sql).toMatch(/NEW\.entropy_precommit_verified_at IS DISTINCT FROM OLD\.entropy_precommit_verified_at/i);
expect(sql).toMatch(/NOT \(OLD\.status = 'pending' AND NEW\.status = 'starting'\)/i);
for (const field of [
'economy', 'variant', 'ruleset_id', 'difficulty_preset', 'max_players',
'seed_hash', 'dungeon', 'start_block_height'
]) {
expect(sql).toMatch(new RegExp(`NEW\\.${field} IS DISTINCT FROM OLD\\.${field}`, 'i'));
}
expect(sql).toMatch(/CREATE TRIGGER trg_matches_immutable_entropy_precommit/i);
});
});