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.
146 lines
6.1 KiB
JavaScript
146 lines
6.1 KiB
JavaScript
const WalletRPCService = require('../src/payments/walletRPCService');
|
|
|
|
const ADDRESSES = Object.freeze({
|
|
'XMR:mainnet': `4${'1'.repeat(94)}`,
|
|
'XMR:stagenet': `5${'1'.repeat(94)}`,
|
|
'XMR:testnet': `9${'1'.repeat(94)}`,
|
|
'WOW:mainnet': `Wo${'1'.repeat(95)}`
|
|
});
|
|
|
|
function walletHttp({ address, network, height = 123, valid = true }) {
|
|
return {
|
|
post: jest.fn(async (_url, body) => {
|
|
if (body.method === 'get_version') return { data: { result: { version: 1 } } };
|
|
if (body.method === 'get_height') return { data: { result: { height } } };
|
|
if (body.method === 'get_address') return { data: { result: { address } } };
|
|
if (body.method === 'validate_address') {
|
|
return { data: { result: { valid, nettype: network } } };
|
|
}
|
|
if (body.method === 'create_address') {
|
|
return { data: { result: { address, address_index: 1 } } };
|
|
}
|
|
throw new Error(`unexpected method: ${body.method}`);
|
|
})
|
|
};
|
|
}
|
|
|
|
describe('WalletRPCService production identity', () => {
|
|
const debugManager = { CONSOLE_LOGGING: false };
|
|
|
|
test.each([
|
|
['XMR', 'mainnet'],
|
|
['XMR', 'stagenet'],
|
|
['XMR', 'testnet'],
|
|
['WOW', 'mainnet']
|
|
])('accepts a validated %s %s primary address', async (cryptoType, network) => {
|
|
const address = ADDRESSES[`${cryptoType}:${network}`];
|
|
const service = new WalletRPCService(debugManager, {
|
|
http: walletHttp({ address, network }),
|
|
walletEndpoint: 'http://wallet.test',
|
|
cryptoType,
|
|
network,
|
|
identityRequired: true
|
|
});
|
|
|
|
await expect(service.initialize()).resolves.toBe(true);
|
|
expect(service.getIdentityStatus()).toMatchObject({
|
|
required: true,
|
|
verified: true,
|
|
expected: { cryptoType, network },
|
|
actual: { cryptoType, network }
|
|
});
|
|
expect(JSON.stringify(service.getHealthStatus())).not.toContain(address);
|
|
});
|
|
|
|
test.each([
|
|
['wrong chain', 'WOW', 'mainnet', ADDRESSES['XMR:mainnet'], 'mainnet'],
|
|
['wrong network', 'XMR', 'mainnet', ADDRESSES['XMR:stagenet'], 'stagenet']
|
|
])('fails initialization for the %s without logging the address', async (
|
|
_label, cryptoType, expectedNetwork, address, actualNetwork
|
|
) => {
|
|
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
const service = new WalletRPCService(debugManager, {
|
|
http: walletHttp({ address, network: actualNetwork }),
|
|
walletEndpoint: 'http://wallet.test',
|
|
cryptoType,
|
|
network: expectedNetwork,
|
|
identityRequired: true
|
|
});
|
|
|
|
await expect(service.initialize()).resolves.toBe(false);
|
|
expect(service.getIdentityStatus().verified).toBe(false);
|
|
expect(error.mock.calls.flat().join(' ')).not.toContain(address);
|
|
error.mockRestore();
|
|
});
|
|
|
|
test('blocks money-changing calls when the configured identity does not match', async () => {
|
|
const http = walletHttp({
|
|
address: ADDRESSES['XMR:mainnet'],
|
|
network: 'mainnet'
|
|
});
|
|
const service = new WalletRPCService(debugManager, {
|
|
http,
|
|
walletEndpoint: 'http://wallet.test',
|
|
cryptoType: 'WOW',
|
|
network: 'mainnet',
|
|
identityRequired: true
|
|
});
|
|
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
await expect(service.createPaymentRequest(1, 'entry', 'user', 'socket'))
|
|
.rejects.toMatchObject({ code: 'WALLET_IDENTITY_MISMATCH' });
|
|
expect(http.post.mock.calls.map(([, body]) => body.method)).not.toContain('create_address');
|
|
expect(error.mock.calls.flat().join(' ')).not.toContain(ADDRESSES['XMR:mainnet']);
|
|
error.mockRestore();
|
|
});
|
|
|
|
test('development may connect for diagnostics but remains explicitly unverified', async () => {
|
|
const service = new WalletRPCService(debugManager, {
|
|
http: walletHttp({ address: ADDRESSES['XMR:stagenet'], network: 'stagenet' }),
|
|
walletEndpoint: 'http://wallet.test',
|
|
cryptoType: 'XMR',
|
|
network: 'mainnet',
|
|
identityRequired: false
|
|
});
|
|
|
|
await expect(service.initialize()).resolves.toBe(true);
|
|
expect(service.getIdentityStatus()).toMatchObject({ required: false, verified: false });
|
|
});
|
|
|
|
test('a transport-only liveness response cannot revive stale production identity', async () => {
|
|
const service = new WalletRPCService(debugManager, {
|
|
http: walletHttp({ address: ADDRESSES['XMR:stagenet'], network: 'stagenet' }),
|
|
walletEndpoint: 'http://wallet.test',
|
|
cryptoType: 'XMR',
|
|
network: 'stagenet',
|
|
identityRequired: true,
|
|
identityMaxAgeMs: 1000
|
|
});
|
|
await expect(service.initialize()).resolves.toBe(true);
|
|
service.networkIdentity.checkedAt = Date.now() - 2000;
|
|
service.isHealthy = false;
|
|
|
|
await expect(service.rpcCall('get_version')).resolves.toBeDefined();
|
|
|
|
expect(service.isHealthy).toBe(false);
|
|
expect(service.getHealthStatus().healthy).toBe(false);
|
|
expect(service.getIdentityStatus().verified).toBe(false);
|
|
});
|
|
|
|
test('rejects a zero-height wallet and redacts URL credentials from health', async () => {
|
|
const error = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
const service = new WalletRPCService(debugManager, {
|
|
http: walletHttp({ address: ADDRESSES['XMR:stagenet'], network: 'stagenet', height: 0 }),
|
|
walletEndpoint: 'http://rpc-user:rpc-password@wallet.test',
|
|
cryptoType: 'XMR',
|
|
network: 'stagenet',
|
|
identityRequired: true
|
|
});
|
|
|
|
await expect(service.initialize()).resolves.toBe(false);
|
|
expect(service.getHealthStatus().endpoint).toBe('http://wallet.test');
|
|
expect(JSON.stringify(service.getHealthStatus())).not.toMatch(/rpc-user|rpc-password/);
|
|
error.mockRestore();
|
|
});
|
|
});
|