v3 : C32 et C33 votent « contre » sur des propositions qu'aucun autre contrôle ne lit

Le texte de Monnaie libre reste tel que C20 et C23 l'attendent (ils passent après dans
l'ordre des fichiers) ; rien n'entre en vigueur, rien ne devient bien coté.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-31 03:50:55 +01:00
co-authored by Claude Opus 5.5
parent 9cc093f283
commit c7d2791ad1
2 changed files with 22 additions and 17 deletions
+20 -15
View File
@@ -5,18 +5,23 @@ import { ALICE, chain, DOC, EVE, person, shot, signIn, VOTE_ACCOUNT, wallet } fr
// Lot B2 — the vote on the Ğ1 chain, after its review: a ballot nothing left for
// is let go; signed, or shown as a Ğecko code, it is kept and the server confirms
// it without the page; the receipt goes to the one who asked, who checks it here.
// The chain is simulated (e2e/chain-mock.mjs): never a real transaction.
// The chain is simulated (e2e/chain-mock.mjs): never a real transaction. The votes go
// to open proposals no other control reads, and only « contre »: nothing enters into
// force, nothing becomes well rated, the other controls find the text as they expect.
type Entry = { id: string; status: string; comment: string };
const mine = async (page: Page): Promise<Entry[]> => (await (await page.request.get('/api/ballots')).json()).ballots;
/** Unfolds the vote of an open proposal, from « Propositions en cours ». */
async function unfold(page: Page, title: string) {
const unit = page.locator('section.text article.unit', { hasText: title });
await unit.hover();
await unit.getByRole('button', { name: 'Le vote' }).click();
// By its title only: the excerpt of another proposal may name it.
await page.locator('ul.deep button.row').filter({ has: page.locator('span.title').getByText(title, { exact: true }) }).click();
return page.getByRole('region', { name: `Le vote de « ${title} »` });
}
const REVOCATION = 'Document de révocation';
const NEVER_ALONE = 'Ne jamais certifier seul';
const answer = (page: Page, yes: boolean) =>
page.evaluate((y) => (window as unknown as { __ldAnswer: (yes: boolean) => void }).__ldAnswer(y), yes);
@@ -34,7 +39,7 @@ test('C32 — rien n’est parti : le bulletin est lâché ; signé ou par Ğeck
}
});
const deleted = () => asked.filter((a) => a.startsWith('DELETE')).length;
const fold = await unfold(page, 'Réciprocité');
const fold = await unfold(page, REVOCATION);
const dialog = page.getByRole('dialog', { name: 'Voter avec ton portefeuille Ğ1' });
// The node does not answer before anything is signed: nothing left, the ballot is let go.
@@ -69,18 +74,18 @@ test('C32 — rien n’est parti : le bulletin est lâché ; signé ou par Ğeck
await expect.poll(async () => (await mine(page)).map((b) => b.status), { timeout: 30_000 }).toEqual(['confirmed']);
expect(deleted()).toBe(2);
await page.goto(DOC);
await expect((await unfold(page, 'Réciprocité')).locator('.voted')).toHaveText('Ton vote : contre · inscrit sur la chaîne Ğ1');
await expect((await unfold(page, REVOCATION)).locator('.voted')).toHaveText('Ton vote : contre · inscrit sur la chaîne Ğ1');
// Ğecko: the code shown, the window closed — the ballot is kept; paid from a phone, confirmed by the server.
const ugo = await person(browser, 'Ugo');
await ugo.goto(DOC);
const theirs = await unfold(ugo, 'Réciprocité');
await theirs.getByRole('button', { name: 'Pour', exact: true }).click();
const theirs = await unfold(ugo, REVOCATION);
await theirs.getByRole('button', { name: 'Contre', exact: true }).click();
const sheet = ugo.getByRole('dialog', { name: 'Voter avec ton portefeuille Ğ1' });
await sheet.getByRole('button', { name: 'Passer par Ğecko' }).click();
const link = (await sheet.getByRole('link', { name: 'Ouvrir dans Ğecko' }).getAttribute('href')) ?? '';
await sheet.getByRole('button', { name: 'Fermer' }).click();
await expect(theirs.getByText('En attente de la chaîne Ğ1 : pour.')).toBeVisible();
await expect(theirs.getByText('En attente de la chaîne Ğ1 : contre.')).toBeVisible();
const { node } = await chain(ugo);
const fields = { address: ALICE.address, dest: VOTE_ACCOUNT, amount: '10', remark: link.split('comment=')[1] ?? '', signature: '0x01' };
await ugo.request.post(node, {
@@ -88,7 +93,7 @@ test('C32 — rien n’est parti : le bulletin est lâché ; signé ou par Ğeck
});
await expect.poll(async () => (await mine(ugo)).map((b) => b.status), { timeout: 30_000 }).toEqual(['confirmed']);
await ugo.goto(DOC);
await expect((await unfold(ugo, 'Réciprocité')).locator('.voted')).toHaveText('Ton vote : pour · inscrit sur la chaîne Ğ1');
await expect((await unfold(ugo, REVOCATION)).locator('.voted')).toHaveText('Ton vote : contre · inscrit sur la chaîne Ğ1');
});
test('C33 — le reçu de ton vote : téléchargé, retrouvé dans tes bulletins, vérifié ici ; jamais chez les autres', async ({ page, browser }) => {
@@ -96,8 +101,8 @@ test('C33 — le reçu de ton vote : téléchargé, retrouvé dans tes bulletins
await signIn(page, 'Ines');
await wallet(page, [ALICE]);
await page.goto(DOC);
const fold = await unfold(page, 'Unicité du compte');
await fold.getByRole('button', { name: 'Pour', exact: true }).click();
const fold = await unfold(page, NEVER_ALONE);
await fold.getByRole('button', { name: 'Contre', exact: true }).click();
const dialog = page.getByRole('dialog', { name: 'Voter avec ton portefeuille Ğ1' });
await expect(dialog.getByText('Ton vote compte : il est inscrit sur la chaîne Ğ1.')).toBeVisible({ timeout: 20_000 });
const [download] = await Promise.all([page.waitForEvent('download'), dialog.getByRole('link', { name: 'Télécharger ton reçu' }).click()]);
@@ -107,7 +112,7 @@ test('C33 — le reçu de ton vote : téléchargé, retrouvé dans tes bulletins
expect(receipt).toMatchObject({ format: 'ld1', status: 'confirmed', voter: 'Alice', author: ALICE.address });
expect(receipt.comment).toMatch(/^ld1:[0-9a-f]{64}$/);
expect(receipt.salt).toMatch(/^[0-9a-f]{32}$/);
expect(receipt.lines).toEqual([expect.objectContaining({ choice: 'for', title: 'Unicité du compte' })]);
expect(receipt.lines).toEqual([expect.objectContaining({ choice: 'against', title: NEVER_ALONE })]);
// The same ballot, in her ballots.
await dialog.getByRole('link', { name: 'Tes bulletins' }).click();
@@ -121,9 +126,9 @@ test('C33 — le reçu de ton vote : téléchargé, retrouvé dans tes bulletins
// Checked here, in the browser, without the server.
await page.getByLabel('Fichier du reçu').setInputFiles(file);
await expect(page.getByText('Le reçu tient : ses votes et son sel donnent bien ce commentaire.')).toBeVisible();
await expect(page.getByText('Unicité du compte : pour')).toBeVisible();
await expect(page.getByText(`${NEVER_ALONE} : contre`)).toBeVisible();
// One choice changed: it no longer holds.
const changed = { ...receipt, lines: receipt.lines.map((l: object) => ({ ...l, choice: 'against' })) };
const changed = { ...receipt, lines: receipt.lines.map((l: object) => ({ ...l, choice: 'for' })) };
await page.getByLabel('Ou colle le reçu').fill(JSON.stringify(changed));
await page.getByRole('button', { name: 'Vérifier', exact: true }).click();
await expect(page.getByText('Le reçu ne tient pas : ses votes et son sel ne donnent pas son commentaire.')).toBeVisible();