forked from yvv/decision
Lot V : sur une base comme la production (migrations jusqu'à 0008), 0009, 0010 et 0011 passent dans l'ordre ; 0010 ne réécrit aucune ligne et le premier démarrage n'écrit rien du tampon
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2199,3 +2199,107 @@ async fn with_the_switch_a_text_entered_by_simulated_votes_is_published() {
|
||||
let fr = texts.french().expect("published");
|
||||
assert!(fr.contains(body_of(&doc, &e4).lines().next().expect("line")), "{fr}");
|
||||
}
|
||||
|
||||
/// Production runs the migrations up to 0008 (M5, `3f10a0e`), its vote on the chain off.
|
||||
/// The image of now applies 0009, 0010 and 0011, in this order; 0010 rewrites no row —
|
||||
/// a ballot of before stays a ballot of the chain, its lines without outcome, the votes
|
||||
/// as they were — and the first start writes nothing of the buffer.
|
||||
#[tokio::test]
|
||||
async fn on_a_base_like_production_the_three_migrations_pass_in_order_and_0010_rewrites_nothing() {
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use std::str::FromStr;
|
||||
let options = SqliteConnectOptions::from_str("sqlite::memory:").expect("url").foreign_keys(true);
|
||||
let pool = sqlx::sqlite::SqlitePoolOptions::new().max_connections(1).connect_with(options).await.expect("pool");
|
||||
let mut before = sqlx::migrate!("./migrations");
|
||||
before.migrations = before.migrations.iter().filter(|m| m.version <= 8).cloned().collect::<Vec<_>>().into();
|
||||
before.run(&pool).await.expect("up to 0008");
|
||||
let tables = |pool: sqlx::SqlitePool| async move {
|
||||
let ballots: Vec<(String, String, Option<i64>)> =
|
||||
sqlx::query_as("SELECT id, status, block FROM ballots ORDER BY id")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("ballots");
|
||||
let lines: Vec<(String, String, String)> =
|
||||
sqlx::query_as("SELECT ballot_id, proposal_id, choice FROM ballot_lines ORDER BY ballot_id")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("lines");
|
||||
let votes: Vec<(String, String, String, String, Option<String>)> = sqlx::query_as(
|
||||
"SELECT proposal_id, identity_id, choice, source, ballot_id FROM votes ORDER BY proposal_id, identity_id",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("votes");
|
||||
(ballots, lines, votes)
|
||||
};
|
||||
// The rows the image of before writes: a document, a ballot of the chain confirmed
|
||||
// with its line and its vote, and one pending.
|
||||
for sql in [
|
||||
"INSERT INTO groups (slug, name, policy, hidden, position) VALUES ('monnaie-libre', 'Monnaie libre', 'g1-members', 0, 0)",
|
||||
"INSERT INTO documents (id, group_slug, slug, title, summary, source_lang, vote_floor, created_at)
|
||||
VALUES ('d1', 'monnaie-libre', 'acte', '{\"fr\":\"Acte\"}', '{}', 'fr', 50, '2026-10-21T00:00:00Z')",
|
||||
"INSERT INTO units (id, doc_id, code, section, position, inertia, created_at) VALUES ('u1', 'd1', 'E4', 'fondamental', 1, '3', '2026-10-21T00:00:00Z')",
|
||||
"INSERT INTO proposals (id, unit_id, kind, title, body, status, origin, revision, created_at, updated_at)
|
||||
VALUES ('p1', 'u1', 'text', 'E4', 'Un texte.', 'open', 'seed', 1, '2026-10-21T00:00:00Z', '2026-10-21T00:00:00Z')",
|
||||
"INSERT INTO identities (id, kind, display_name, created_at) VALUES ('dev:chiara', 'dev', 'Chiara', '2026-10-21T00:00:00Z')",
|
||||
"INSERT INTO identities (id, kind, display_name, g1_member, created_at) VALUES ('idty:9001', 'g1', 'Alice', 1, '2026-10-21T00:00:00Z')",
|
||||
"INSERT INTO ballots (id, doc_id, requested_by, salt, digest, remark_hash, account, amount, status, voter_id, block,
|
||||
event_index, created_at, expires_at, settled_at)
|
||||
VALUES ('b1', 'd1', 'dev:chiara', 's1', 'h1', '0xr1', 'g1vote', 10, 'confirmed', 'idty:9001', 100, 1,
|
||||
'2026-11-01T00:00:00Z', '2026-11-02T00:00:00Z', '2026-11-01T00:01:00Z')",
|
||||
"INSERT INTO ballot_lines (ballot_id, proposal_id, revision, choice) VALUES ('b1', 'p1', 1, 'for')",
|
||||
"INSERT INTO votes (proposal_id, identity_id, choice, revision, cast_at, source, block, event_index, ballot_id)
|
||||
VALUES ('p1', 'idty:9001', 'for', 1, '2026-11-01T00:01:00Z', 'chain', 100, 1, 'b1')",
|
||||
"INSERT INTO ballots (id, doc_id, requested_by, salt, digest, remark_hash, account, amount, status, created_at, expires_at)
|
||||
VALUES ('b2', 'd1', 'dev:chiara', 's2', 'h2', '0xr2', 'g1vote', 10, 'pending', '2026-11-03T00:00:00Z', '2099-01-01T00:00:00Z')",
|
||||
"INSERT INTO ballot_lines (ballot_id, proposal_id, revision, choice) VALUES ('b2', 'p1', 1, 'against')",
|
||||
] {
|
||||
sqlx::query(sql).execute(&pool).await.unwrap_or_else(|e| panic!("{sql}: {e}"));
|
||||
}
|
||||
let written = tables(pool.clone()).await;
|
||||
// The image of now.
|
||||
let state = AppState::new(Config::for_tests(), pool.clone()).await.expect("state");
|
||||
let mut all = sqlx::migrate!("./migrations");
|
||||
all.set_ignore_missing(true);
|
||||
all.run(&pool).await.expect("0009 to 0011");
|
||||
let order: Vec<i64> = sqlx::query_scalar("SELECT version FROM _sqlx_migrations ORDER BY rowid")
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("order");
|
||||
assert_eq!(order, (1..=11).collect::<Vec<i64>>(), "0009, then 0010, then 0011");
|
||||
assert_eq!(tables(pool.clone()).await, written, "no row rewritten");
|
||||
let added: Vec<(String, String, Option<String>)> = sqlx::query_as(
|
||||
"SELECT b.id, b.mode, l.outcome FROM ballots b JOIN ballot_lines l ON l.ballot_id = b.id ORDER BY b.id",
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.expect("columns");
|
||||
assert_eq!(added, [("b1".into(), "chain".into(), None), ("b2".into(), "chain".into(), None)]);
|
||||
let index: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = 'ballots_doc'")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.expect("index");
|
||||
assert_eq!(index, 1);
|
||||
// The first start, the chain closed: the watcher passes, nothing of the buffer is written.
|
||||
ballots::sweep(&state).await.expect("sweep");
|
||||
assert_eq!(tables(pool.clone()).await, written);
|
||||
let simulated: i64 = sqlx::query_scalar(
|
||||
"SELECT (SELECT COUNT(*) FROM ballots WHERE mode = 'simulated') + (SELECT COUNT(*) FROM votes WHERE source = 'simulated')
|
||||
+ (SELECT COUNT(*) FROM messages WHERE topic = 'ballot')",
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.expect("simulated");
|
||||
assert_eq!(simulated, 0);
|
||||
// The ballots of before read as they did: a vote of the chain, recorded; the one
|
||||
// pending, an intention that takes its place in the totals shown only.
|
||||
let mine = ballots::my_votes(&state, "dev:chiara", "d1").await.expect("mine");
|
||||
assert_eq!(mine.get("p1").map(|v| (v.choice.as_str(), v.source.as_str())), Some(("for", "chain")));
|
||||
let st = ld_server::docs::load_state(&state, "d1").await.expect("state");
|
||||
let pair = |t: ld_engine::threshold::Tally| (t.for_votes, t.against);
|
||||
assert_eq!(
|
||||
(pair(st.tally("p1")), pair(st.shown_tally("p1")), pair(st.pending_tally("p1"))),
|
||||
((1, 0), (0, 1), (0, 1))
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user