Lot V : contrôle C34 (la chaîne fermée, Monnaie libre vote quand même : le vote simulé compte aussitôt, marqué dans « Tes bulletins ») ; C24 dit que le serveur sans compte des votes simule depuis le lot V ; CLAUDE.md et CONTROLES.md (buffer, casts_simulated, simulation.rs, C34, 35 contrôles / 36 tests)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 06:03:33 +01:00
co-authored by Claude Opus 5.5
parent f62347cc6b
commit c06c37a24a
3 changed files with 61 additions and 7 deletions
+50 -2
View File
@@ -176,8 +176,9 @@ test('C23 — le vote par la chaîne Ğ1 : un geste, confirmé par l’indexeur
test('C24 — sans compte de vote, le vote par la chaîne est fermé et le dit ; le groupe d’essai vote', async ({ page }) => {
await signIn(page, 'Chiara');
// The e2e server has its vote account: the page is shown what a server without one answers
// (the server itself: `without_a_vote_account_the_chain_vote_is_off_and_the_test_group_still_votes`).
// The e2e server has its vote account: the page is shown a vote closed, as a server without
// one answered before lot V. Since lot V (ADR 0012) a server without one votes in simulation
// instead (the server: `without_a_vote_account_monnaie_libre_votes_in_simulation_…`; here, C34).
await page.route(/\/api\/docs\/[^/?]+$/, async (route) => {
const res = await route.fetch();
const body = await res.json();
@@ -213,3 +214,50 @@ test('C24 — sans compte de vote, le vote par la chaîne est fermé et le dit ;
await unit.getByRole('button', { name: 'Le vote' }).click();
await expect(unit.locator('section.fold').getByRole('button', { name: 'Pour', exact: true })).toBeVisible();
});
/** The version of an article of Monnaie libre, by its title, as the API tells it to `page`. */
async function version(page: import('@playwright/test').Page, title: string) {
const g = await (await page.request.get('/api/groups/monnaie-libre')).json();
const d = await (await page.request.get(`/api/docs/${g.docs[0].id}`)).json();
type P = { title: string; tally: { against: number }; tallySimulated: { against: number }; myVoteState: string | null };
const all: P[] = d.units.flatMap((u: { proposals: P[] }) => u.proposals);
const p = all.find((x) => x.title === title);
if (!p) throw new Error(`no version « ${title} »`);
return { vote: d.vote as { way: string; open: boolean; simulated: boolean }, p };
}
test('C34 — la chaîne fermée, Monnaie libre vote quand même : le vote simulé compte aussitôt, marqué « simulé » dans tes bulletins', async ({ page }) => {
// Lot V (ADR 0012, Yvv 2026-11-02: « Il compte, puis s'efface »). The e2e server has its
// chain: it is closed for this control only, then opened again. The vote goes to a
// proposal no other control reads, and only « contre »: nothing enters into force.
await signIn(page, 'Sacha');
const chainOpen = async (open: boolean) =>
(await (await page.request.post('/api/dev/vote-chain', { data: { open }, headers: { 'x-requested-with': 'libredecision' } })).json()).open;
const title = 'Logiciel auditable';
expect(await chainOpen(false)).toBe(false);
try {
const before = await version(page, title);
expect(before.vote).toMatchObject({ way: 'server', open: true, simulated: true });
await page.goto(DOC);
// The basket is there, without a wallet: the vote leaves by the server.
await expect(page.getByRole('button', { name: /^Voter en lot/ })).toBeVisible();
await deepRow(page, title).click();
const fold = page.getByRole('region', { name: `Le vote de « ${title} »` });
await fold.getByRole('button', { name: 'Contre', exact: true }).click();
await expect(page.getByRole('dialog', { name: 'Voter avec ton portefeuille Ğ1' })).toHaveCount(0);
await expect(fold.locator('.voted')).toHaveText(/^Ton vote : contre/);
await expect(fold.getByText(`0 pour · ${before.p.tally.against + 1} contre`)).toBeVisible();
// Counted at once, marked as simulated.
const after = await version(page, title);
expect(after.p.myVoteState).toBe('simulated');
expect(after.p.tallySimulated.against).toBe(before.p.tallySimulated.against + 1);
// In her ballots: simulated, counted for her session, never a block of the chain.
await page.goto('/bulletins');
const ballot = page.locator('li.ballot').first();
await expect(ballot.locator('.state')).toHaveText("Simulé : compté pour « Sacha » jusqu'à l'ouverture de la chaîne Ğ1");
await expect(ballot.getByText('bloc', { exact: false })).toHaveCount(0);
} finally {
// The next controls need the chain.
expect(await chainOpen(true)).toBe(true);
}
});