185 lines
5.5 KiB
TypeScript
185 lines
5.5 KiB
TypeScript
import { validateSync } from 'class-validator';
|
|
import { MoneroNetwork } from '../../types/MoneroNetwork';
|
|
import { IsMoneroStandardAddress } from './isMoneroStandardAddress';
|
|
|
|
class TestDto {
|
|
@IsMoneroStandardAddress()
|
|
destinationAddress: string;
|
|
}
|
|
|
|
const pad = (prefix: string) => `${prefix}${'A'.repeat(95 - prefix.length)}`;
|
|
|
|
const MAINNET_STANDARD_ADDRESS =
|
|
'4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRj5UzqtReoS44qo9mtmXCqY45DJ852K5Jv2684Rge';
|
|
|
|
const validateAddress = (value: string) => {
|
|
const dto = Object.assign(new TestDto(), { destinationAddress: value });
|
|
|
|
return validateSync(dto);
|
|
};
|
|
|
|
const accepts = (value: string) => {
|
|
expect(validateAddress(value)).toHaveLength(0);
|
|
};
|
|
|
|
const rejects = (value: string) => {
|
|
expect(validateAddress(value).length).toBeGreaterThan(0);
|
|
};
|
|
|
|
describe('IsMoneroStandardAddress', () => {
|
|
const originalNetwork = process.env.MONERO_NETWORK;
|
|
|
|
afterEach(() => {
|
|
if (originalNetwork === undefined) {
|
|
delete process.env.MONERO_NETWORK;
|
|
} else {
|
|
process.env.MONERO_NETWORK = originalNetwork;
|
|
}
|
|
});
|
|
|
|
describe('mainnet', () => {
|
|
beforeEach(() => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Mainnet;
|
|
});
|
|
|
|
it('accepts a real mainnet standard address', () => {
|
|
accepts(MAINNET_STANDARD_ADDRESS);
|
|
});
|
|
|
|
it('accepts mainnet standard addresses', () => {
|
|
accepts(pad('41'));
|
|
accepts(pad('49'));
|
|
accepts(pad('4A'));
|
|
accepts(pad('4B'));
|
|
});
|
|
|
|
it('accepts mainnet subaddresses', () => {
|
|
accepts(pad('82'));
|
|
accepts(pad('89'));
|
|
accepts(pad('8A'));
|
|
accepts(pad('8B'));
|
|
accepts(pad('8C'));
|
|
});
|
|
|
|
it('rejects stagenet addresses', () => {
|
|
rejects(pad('51'));
|
|
rejects(pad('72'));
|
|
});
|
|
|
|
it('rejects testnet addresses', () => {
|
|
rejects(pad('91'));
|
|
rejects(pad('BY'));
|
|
});
|
|
});
|
|
|
|
describe('stagenet', () => {
|
|
beforeEach(() => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Stagenet;
|
|
});
|
|
|
|
it('accepts stagenet standard addresses', () => {
|
|
accepts(pad('51'));
|
|
accepts(pad('59'));
|
|
accepts(pad('5A'));
|
|
accepts(pad('5B'));
|
|
});
|
|
|
|
it('accepts stagenet subaddresses', () => {
|
|
accepts(pad('72'));
|
|
accepts(pad('79'));
|
|
accepts(pad('7A'));
|
|
accepts(pad('7B'));
|
|
});
|
|
|
|
it('rejects mainnet addresses', () => {
|
|
rejects(MAINNET_STANDARD_ADDRESS);
|
|
rejects(pad('4A'));
|
|
rejects(pad('82'));
|
|
});
|
|
|
|
it('rejects testnet addresses', () => {
|
|
rejects(pad('91'));
|
|
rejects(pad('BY'));
|
|
});
|
|
});
|
|
|
|
describe('shared validation rules', () => {
|
|
beforeEach(() => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Mainnet;
|
|
});
|
|
|
|
it('accepts lowercase base58 characters in the body', () => {
|
|
accepts('4AdUndXHHZ6cfufTMvppY6JwXNouMBzSkbLYfpAV5Usx3skxNgYeYTRj5UzqtReoS44qo9mtmXCqY45DJ852K5Jv2684Rge');
|
|
});
|
|
|
|
it('trims surrounding whitespace on mainnet', () => {
|
|
accepts(` ${pad('4A')} `);
|
|
});
|
|
|
|
it('trims surrounding whitespace on stagenet', () => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Stagenet;
|
|
|
|
accepts(`\n${pad('5B')}\t`);
|
|
});
|
|
|
|
it.each(['0', '1', '2', '3', '6', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G'])(
|
|
'rejects addresses starting with %s',
|
|
prefix => {
|
|
rejects(pad(`${prefix}A`));
|
|
}
|
|
);
|
|
|
|
it.each(['0', 'C'])('rejects mainnet standard addresses with second character %s', second => {
|
|
rejects(pad(`4${second}`));
|
|
});
|
|
|
|
it.each(['0', '1'])('rejects mainnet subaddresses with second character %s', second => {
|
|
rejects(pad(`8${second}`));
|
|
});
|
|
|
|
it.each(['0', 'C'])('rejects stagenet standard addresses with second character %s', second => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Stagenet;
|
|
|
|
rejects(pad(`5${second}`));
|
|
});
|
|
|
|
it.each(['0', '1', 'C'])('rejects stagenet subaddresses with second character %s', second => {
|
|
process.env.MONERO_NETWORK = MoneroNetwork.Stagenet;
|
|
|
|
rejects(pad(`7${second}`));
|
|
});
|
|
|
|
it('rejects empty input', () => {
|
|
rejects('');
|
|
});
|
|
|
|
it('rejects whitespace-only input', () => {
|
|
rejects(' ');
|
|
});
|
|
|
|
it('rejects addresses that are too short', () => {
|
|
rejects(pad('4A').slice(0, 94));
|
|
});
|
|
|
|
it('rejects addresses that are too long', () => {
|
|
rejects(`${pad('4A')}A`);
|
|
});
|
|
|
|
it('rejects integrated addresses (106 chars, also start with 4 on mainnet)', () => {
|
|
rejects(`${MAINNET_STANDARD_ADDRESS}${'A'.repeat(11)}`);
|
|
});
|
|
|
|
it.each(['0', 'O', 'I', 'l'])('rejects addresses containing %s', invalidChar => {
|
|
const address = `${pad('4A').slice(0, 10)}${invalidChar}${pad('4A').slice(11)}`;
|
|
|
|
rejects(address);
|
|
});
|
|
|
|
it('returns a network-specific error message', () => {
|
|
const [error] = validateAddress('invalid');
|
|
|
|
expect(error.constraints?.isMoneroStandardAddress).toBe('Enter a valid mainnet Monero address.');
|
|
});
|
|
});
|
|
});
|