Files
decision/web/tests/unit/wallet.test.ts
T
YvvandClaude Opus 5.5 9c8f4cc568 v3 : relecture de 7ffa4fd — un refus du nœud lâche le bulletin, le reçu ne dit vérifié que ce que scelle l'empreinte
polkadot-api lève InvalidTxError (JSON indenté) quand le nœud refuse la transaction :
elle est désormais lue comme « la chaîne a dit non » (rien n'est parti, le bulletin est
lâché), au lieu de rester en attente 24 h. La page /bulletins ne présente comme vérifiés
que la version, la révision et le choix ; l'article, le titre et le bloc du reçu sont dits
hors du sceau. ADR 0008 : trois affirmations corrigées (indexeur sans filtre, débit de la
tâche de fond, lectures qui n'utilisent pas encore shown_tally).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:46:28 +01:00

65 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { InvalidTxError } from 'polkadot-api';
import { amountText, geckoLink } from '../../src/lib/wallet/gecko';
import { failure, injectedNames, mayLetGo, testExtrinsic } from '../../src/lib/wallet/injected';
const ACCOUNT = 'g1L5EYvvSBaYuQCPW78bHxYcvHfmWAqHYe3Gsrp9SV4kWZaEM';
const COMMENT = `ld1:${'ab'.repeat(32)}`;
describe('Ğecko', () => {
it('the link and the QR code carry the account, the amount in Ğ1 and the sealed comment', () => {
expect(geckoLink(ACCOUNT, 10, COMMENT)).toBe(`g1://${ACCOUNT}?amount=0.1&comment=${COMMENT}`);
// Ğ1nkgo keeps [A-Za-z0-9 .;:!?()-_@&<>%]: the comment passes untouched.
expect(/^[A-Za-z0-9 .;:!?()\-_@&<>%]+$/.test(COMMENT)).toBe(true);
});
it('amounts in cents read as Ğ1', () => {
expect([amountText(10), amountText(100), amountText(125), amountText(1)]).toEqual(['0.1', '1', '1.25', '0.01']);
});
});
describe('wallets of the page', () => {
it('every extension offering the standard interface is found, the one for Ğ1 first', () => {
const win = { injectedWeb3: { 'polkadot-js': { enable: async () => ({}) }, 'duniter-connect': { enable: async () => ({}) }, broken: {} } };
expect(injectedNames(win)).toEqual(['duniter-connect', 'polkadot-js']);
expect(injectedNames({})).toEqual([]);
});
it('what a wallet answers is told in words', () => {
expect(failure(new Error('Cancelled'))).toBe('cancelled');
expect(failure(new Error('Rejected by user'))).toBe('cancelled');
expect(failure({ type: 'Invalid', value: { type: 'Payment' } })).toBe('funds');
expect(failure(new Error('Inability to pay some fees (e.g. account balance too low)'))).toBe('funds');
expect(failure(new Error('socket hang up'))).toBe('wallet');
// Refused by the pool of the node: it will never be included.
expect(failure({ type: 'Invalid', value: { type: 'Stale' } })).toBe('chain');
expect(failure({ code: 1010, message: 'Invalid Transaction' })).toBe('chain');
});
it('a refusal of the node, as polkadot-api raises it, is the chain saying no (relecture of 7ffa4fd)', () => {
// The real class: its message is indented JSON (`"type": "Invalid"`, with a space).
for (const reason of ['Stale', 'AncientBirthBlock', 'BadProof', 'ExhaustsResources', 'Future']) {
const e = new InvalidTxError({ type: 'Invalid', value: { type: reason } });
expect(failure(e), reason).toBe('chain');
expect(mayLetGo(failure(e), true), reason).toBe(true);
}
expect(failure(new InvalidTxError({ type: 'Invalid', value: { type: 'Payment' } }))).toBe('funds');
expect(failure(new InvalidTxError({ type: 'Unknown', value: { type: 'CannotLookup' } }))).toBe('chain');
});
it('a ballot is let go when nothing left the page, or when the chain said no — kept when it may still land', () => {
// Before the signature (the node down before sending, a refusal, the window closed): nothing left.
for (const f of ['node', 'wallet', 'cancelled', 'funds', 'account', 'chain'] as const) expect(mayLetGo(f, false), f).toBe(true);
// Signed and handed to the node: it may reach the chain, unless the chain refused it.
for (const f of ['funds', 'account', 'chain'] as const) expect(mayLetGo(f, true), f).toBe(true);
for (const f of ['node', 'wallet'] as const) expect(mayLetGo(f, true), f).toBe(false);
});
it('in tests, the fake node receives what was signed, readable', () => {
const hex = testExtrinsic({ address: '5Grw', dest: ACCOUNT, amount: '10', remark: COMMENT, signature: '0x01' });
expect(hex.startsWith('0x')).toBe(true);
const back = JSON.parse(new TextDecoder().decode(Uint8Array.from((hex.slice(2).match(/../g) ?? []).map((b) => parseInt(b, 16)))));
expect(back).toEqual({ address: '5Grw', dest: ACCOUNT, amount: '10', remark: COMMENT, signature: '0x01' });
});
});