forked from yvv/decision
Lot T4 : les tests d'avant suivent le seed de T4 sans rien perdre de ce qu'ils prouvent
Les tests qui lisaient F, N ou O rejouent leurs transitions sur le seed de M8 (tests/fixtures/seed-m8) : seed_quorum_texts, seed_rules, seed_proposals, api, quorum ; ils disent en plus que ces amendements sont dépassés par le retrait (superseded_by). Les comptes suivent le document de T4 : 27 articles, quatre Propositions seed (I1, E1, E12, E2), 33 modules à voter (27 articles, le titre, l'ordre de 5 sections). modules.rs : l'ordre sous vote se rejoue sur « conclusion » (K1, K2) ; « un ordre d'un seul article n'a rien à décider » sur une base née du seed de M8 (O1). seed_amend : tests unitaires de Version::SeedProposal et de la ligne seed_proposal (put_forward). order.rs ne lit que les colonnes d'avant 0012 : sections_now ne sert qu'à la naissance des modules, et article_amend n'en garde que les articles vivants. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,8 +42,7 @@ const LOW: u8 = ld_server::seed::SEED_PROPOSAL_LEVEL;
|
||||
/// he wrote « il n'y a une et une seule » (read as a slip, the parallel of the first half);
|
||||
/// the quotation keeps the non-breaking spaces of the one it replaces.
|
||||
const I1_FR: &str = "*«\u{a0}Derrière une clé il y a un et un seul être humain vivant\u{a0}; derrière un être humain, il y a une et une seule clé certifiante.\u{a0}»*\n\nC'est l'unique enjeu de la certification et de votre engagement, il donne à notre toile fiduciaire sa valeur : pouvoir nous fier à l'unicité et l'intégrité de tout geste signé par cette clé.\n\nCet acte d'engagement est par ailleurs le seul acte de nature contractuelle, qui fonde l'usage de la première monnaie libre.";
|
||||
const E1_FR: &str =
|
||||
"Je m'engage sur l'honneur à n'avoir et n'avoir jamais qu'un seul et unique compte certifiant.";
|
||||
const E1_FR: &str = "Je m'engage sur l'honneur à n'avoir et n'avoir jamais qu'un seul et unique compte certifiant.";
|
||||
const E12_TITLE: [(&str, &str); 3] =
|
||||
[("fr", "Responsabilité civique"), ("en", "Civic responsibility"), ("es", "Responsabilidad cívica")];
|
||||
const E12_FR: &str = "Je déclare sur l'honneur avoir la capacité légale et physique de signer un contrat et d'en assumer la responsabilité civique, pour certifier à mon tour.";
|
||||
@@ -364,22 +363,28 @@ async fn rows(state: &AppState, table: &str, skip: &[&str], filter: &str) -> Vec
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.expect("columns");
|
||||
let cols: Vec<String> = columns.iter().filter(|c| !skip.contains(&c.as_str())).map(|c| format!("quote(\"{c}\")")).collect();
|
||||
let cols: Vec<String> =
|
||||
columns.iter().filter(|c| !skip.contains(&c.as_str())).map(|c| format!("quote(\"{c}\")")).collect();
|
||||
let sql = format!("SELECT {} FROM \"{table}\" WHERE {filter} ORDER BY rowid", cols.join(" || '|' || "));
|
||||
sqlx::query_scalar(&sql).fetch_all(&state.db).await.expect("rows")
|
||||
}
|
||||
|
||||
/// The units that leave, by section (their articles and their orders).
|
||||
const OF_THE_OUT: &str = "section IN ('formule', 'inertie', 'ordonnancement')";
|
||||
const OF_THEIR_VERSIONS: &str =
|
||||
"proposal_id IN (SELECT p.id FROM proposals p JOIN units u ON u.id = p.unit_id WHERE u.section IN ('formule', 'inertie', 'ordonnancement'))";
|
||||
const OF_THEIR_VERSIONS: &str = "proposal_id IN (SELECT p.id FROM proposals p JOIN units u ON u.id = p.unit_id WHERE u.section IN ('formule', 'inertie', 'ordonnancement'))";
|
||||
|
||||
/// What the articles of operation hold: units (without the columns of the retirement),
|
||||
/// their versions, revisions, translations.
|
||||
async fn the_rules_of_operation(state: &AppState) -> [Vec<String>; 4] {
|
||||
[
|
||||
rows(state, "units", &["retired_at", "retired_reason"], OF_THE_OUT).await,
|
||||
rows(state, "proposals", &[], "unit_id IN (SELECT id FROM units WHERE section IN ('formule', 'inertie', 'ordonnancement'))").await,
|
||||
rows(
|
||||
state,
|
||||
"proposals",
|
||||
&[],
|
||||
"unit_id IN (SELECT id FROM units WHERE section IN ('formule', 'inertie', 'ordonnancement'))",
|
||||
)
|
||||
.await,
|
||||
rows(state, "revisions", &[], OF_THEIR_VERSIONS).await,
|
||||
rows(state, "translations", &[], OF_THEIR_VERSIONS).await,
|
||||
]
|
||||
@@ -461,7 +466,11 @@ async fn a_new_base_is_born_with_the_texts_of_t4_and_without_the_rules_of_operat
|
||||
let u = unit(&doc, code).expect(code);
|
||||
let p = &u["proposals"][0];
|
||||
assert_eq!(p["body"], new_text(code), "{group} {code}");
|
||||
assert_eq!((p["status"].as_str(), p["seedProposal"].as_bool()), (Some("open"), Some(true)), "{group} {code}");
|
||||
assert_eq!(
|
||||
(p["status"].as_str(), p["seedProposal"].as_bool()),
|
||||
(Some("open"), Some(true)),
|
||||
"{group} {code}"
|
||||
);
|
||||
assert_eq!(p["revision"], 1, "{group} {code}");
|
||||
assert_eq!(u["inertia"], LOW, "{group} {code}");
|
||||
assert!(u["inForce"].is_null(), "{group} {code}");
|
||||
@@ -489,7 +498,10 @@ async fn a_new_base_is_born_with_the_texts_of_t4_and_without_the_rules_of_operat
|
||||
let present = |g: &str| (g.to_owned(), "present".to_owned(), None, None);
|
||||
assert_eq!(retirements(&state).await, [present("essai"), present("monnaie-libre")]);
|
||||
let added = additions(&state).await;
|
||||
assert_eq!(added.iter().map(|a| (a.0.as_str(), a.1.as_str())).collect::<Vec<_>>(), [("essai", "present"), ("monnaie-libre", "present")]);
|
||||
assert_eq!(
|
||||
added.iter().map(|a| (a.0.as_str(), a.1.as_str())).collect::<Vec<_>>(),
|
||||
[("essai", "present"), ("monnaie-libre", "present")]
|
||||
);
|
||||
let rw = rewrites(&state).await;
|
||||
assert_eq!(rw.len(), 6, "{rw:?}");
|
||||
assert!(rw.iter().all(|(_, _, o, _)| o == "present"), "{rw:?}");
|
||||
@@ -507,11 +519,8 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
for code in AMORCE {
|
||||
let (id, status, revision, _, _, featured) = seed_version(&base, group, code).await;
|
||||
assert_eq!((status.as_str(), featured), ("open", true), "{group} {code}");
|
||||
let expected: &[&str] = if code == "I1" {
|
||||
&["initial", "amendment", "seed_proposal"]
|
||||
} else {
|
||||
&["initial", "seed_proposal"]
|
||||
};
|
||||
let expected: &[&str] =
|
||||
if code == "I1" { &["initial", "amendment", "seed_proposal"] } else { &["initial", "seed_proposal"] };
|
||||
assert_eq!(kinds(&base, &id).await, expected, "{group} {code}");
|
||||
assert_eq!(revision, if code == "I1" { 2 } else { 1 }, "{group} {code}");
|
||||
}
|
||||
@@ -523,7 +532,9 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
let before = tables(&base).await;
|
||||
let before_rows = rowids(&base).await;
|
||||
let kept = the_rules_of_operation(&base).await;
|
||||
let untouched_versions = rows(&base, "proposals", &[], "unit_id NOT IN (SELECT u.id FROM units u WHERE u.code IN ('I1', 'E1', 'E2'))").await;
|
||||
let untouched_versions =
|
||||
rows(&base, "proposals", &[], "unit_id NOT IN (SELECT u.id FROM units u WHERE u.code IN ('I1', 'E1', 'E2'))")
|
||||
.await;
|
||||
|
||||
let state = t4(&base).await;
|
||||
// Taken at once, before anyone signs in (a session writes its identity).
|
||||
@@ -547,7 +558,11 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
let v = &seed_unit(&today, code)["versions"][0];
|
||||
for lang in ["en", "es"] {
|
||||
let (t, b, hash, origin, by) = translation(&state, &id, lang).await;
|
||||
assert_eq!((json!(t), json!(b)), (v["title"][lang].clone(), v["body"][lang].clone()), "{group} {code} {lang}");
|
||||
assert_eq!(
|
||||
(json!(t), json!(b)),
|
||||
(v["title"][lang].clone(), v["body"][lang].clone()),
|
||||
"{group} {code} {lang}"
|
||||
);
|
||||
assert_eq!(hash, ld_server::seed_amend::fingerprint(&title, &body), "{group} {code} {lang}");
|
||||
assert_eq!((origin.as_str(), by), ("seed", None));
|
||||
}
|
||||
@@ -565,14 +580,17 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
assert_eq!((title.as_str(), body.as_str()), ("Responsabilité civique", E12_FR), "{group}");
|
||||
assert_eq!((status.as_str(), revision, featured), ("open", 1, true), "{group}");
|
||||
assert_eq!(kinds(&state, &id).await, ["initial"], "{group}");
|
||||
let (section, position, inertia, in_force): (String, f64, String, Option<String>) = sqlx::query_as(
|
||||
"SELECT section, position, inertia, in_force_id FROM units WHERE id = ?",
|
||||
)
|
||||
.bind(unit_id(&state, group, NEW).await)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.expect("unit");
|
||||
assert_eq!((section.as_str(), position, inertia, in_force), ("fondamental", 4.5, LOW.to_string(), None), "{group}");
|
||||
let (section, position, inertia, in_force): (String, f64, String, Option<String>) =
|
||||
sqlx::query_as("SELECT section, position, inertia, in_force_id FROM units WHERE id = ?")
|
||||
.bind(unit_id(&state, group, NEW).await)
|
||||
.fetch_one(&state.db)
|
||||
.await
|
||||
.expect("unit");
|
||||
assert_eq!(
|
||||
(section.as_str(), position, inertia, in_force),
|
||||
("fondamental", 4.5, LOW.to_string(), None),
|
||||
"{group}"
|
||||
);
|
||||
for (lang, t) in E12_TITLE.iter().skip(1) {
|
||||
let (title_tr, _, hash, origin, _) = translation(&state, &id, lang).await;
|
||||
assert_eq!((title_tr.as_str(), origin.as_str()), (*t, "seed"), "{group} {lang}");
|
||||
@@ -586,10 +604,11 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
for group in GROUPS {
|
||||
assert_eq!(retired(&state, group).await, all_out(), "{group}");
|
||||
}
|
||||
let reasons: Vec<String> = sqlx::query_scalar(&format!("SELECT DISTINCT retired_reason FROM units WHERE {OF_THE_OUT}"))
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.expect("reasons");
|
||||
let reasons: Vec<String> =
|
||||
sqlx::query_scalar(&format!("SELECT DISTINCT retired_reason FROM units WHERE {OF_THE_OUT}"))
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.expect("reasons");
|
||||
assert_eq!(reasons, [RETIRE]);
|
||||
assert_eq!(the_rules_of_operation(&state).await, kept, "their rows, as they were");
|
||||
|
||||
@@ -636,9 +655,22 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
let was = before.get(t).copied().unwrap_or(0);
|
||||
assert_eq!(n - was, added.get(t.as_str()).copied().unwrap_or(0), "{t}: {was} → {n}");
|
||||
}
|
||||
eprintln!("lot T4, rows by table (before → after): {}", after.iter().map(|(t, n)| format!("{t} {} → {n}", before.get(t).copied().unwrap_or(0))).collect::<Vec<_>>().join(", "));
|
||||
eprintln!(
|
||||
"lot T4, rows by table (before → after): {}",
|
||||
after
|
||||
.iter()
|
||||
.map(|(t, n)| format!("{t} {} → {n}", before.get(t).copied().unwrap_or(0)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
// Every other version untouched.
|
||||
let others = rows(&state, "proposals", &[], "unit_id NOT IN (SELECT u.id FROM units u WHERE u.code IN ('I1', 'E1', 'E2', 'E12'))").await;
|
||||
let others = rows(
|
||||
&state,
|
||||
"proposals",
|
||||
&[],
|
||||
"unit_id NOT IN (SELECT u.id FROM units u WHERE u.code IN ('I1', 'E1', 'E2', 'E12'))",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(others, untouched_versions);
|
||||
// The queue of Monnaie libre: a row for each change, waiting for the first text.
|
||||
let queued: Vec<(String, String)> = sqlx::query_as(
|
||||
@@ -657,7 +689,10 @@ async fn production_gets_lot_t4_once(base: AppState, rules_revision: i64) {
|
||||
let again = t4(&state).await;
|
||||
assert_eq!(tables(&again).await, settled);
|
||||
let unchanged = |t: &BTreeMap<String, i64>| {
|
||||
t.iter().filter(|(k, _)| !matches!(k.as_str(), "identities" | "sessions" | "memberships")).map(|(k, n)| (k.clone(), *n)).collect::<Vec<_>>()
|
||||
t.iter()
|
||||
.filter(|(k, _)| !matches!(k.as_str(), "identities" | "sessions" | "memberships"))
|
||||
.map(|(k, n)| (k.clone(), *n))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
assert_eq!(unchanged(&settled), unchanged(&after));
|
||||
assert_eq!(retirements(&again).await, [mark("essai"), mark("monnaie-libre")]);
|
||||
@@ -843,7 +878,8 @@ async fn leave(state: &AppState, trace: Trace) {
|
||||
Trace::Typo => {
|
||||
let (p, _, _, title, body, _) = id("F1.1").await;
|
||||
let fixed = body.replacen('\'', "\u{2019}", 1);
|
||||
let v = vera.ok("PATCH", &format!("/api/proposals/{p}"), Some(json!({ "title": title, "body": fixed }))).await;
|
||||
let v =
|
||||
vera.ok("PATCH", &format!("/api/proposals/{p}"), Some(json!({ "title": title, "body": fixed }))).await;
|
||||
assert_eq!(v["edit"], "typo_pending", "{v}");
|
||||
}
|
||||
Trace::Translation => {
|
||||
@@ -1002,7 +1038,8 @@ async fn an_image_that_ignores_the_column_shows_the_rules_of_operation_as_they_w
|
||||
let base = production_after_m8("rollback").await;
|
||||
let mut c = Client::as_(&base, "Chiara", false).await;
|
||||
let view = |doc: &Value| -> Vec<Value> {
|
||||
let units = doc["units"].as_array().expect("units").iter().filter(|u| OUT.contains(&u["code"].as_str().unwrap_or("")));
|
||||
let units =
|
||||
doc["units"].as_array().expect("units").iter().filter(|u| OUT.contains(&u["code"].as_str().unwrap_or("")));
|
||||
let orders = doc["modules"]["orders"]
|
||||
.as_array()
|
||||
.expect("orders")
|
||||
@@ -1021,7 +1058,10 @@ async fn an_image_that_ignores_the_column_shows_the_rules_of_operation_as_they_w
|
||||
assert!(view(&c.doc(group).await).is_empty(), "{group}: retired");
|
||||
}
|
||||
// The image of before (de1b7eb) reads `units` without the column: as here with it empty.
|
||||
sqlx::query("UPDATE units SET retired_at = NULL").execute(&state.db).await.expect("as the image of before reads it");
|
||||
sqlx::query("UPDATE units SET retired_at = NULL")
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.expect("as the image of before reads it");
|
||||
let mut c = Client::as_(&state, "Chiara", false).await;
|
||||
for (i, group) in GROUPS.iter().enumerate() {
|
||||
assert_eq!(view(&c.doc(group).await), before[i], "{group}: the same units, versions, texts, translations");
|
||||
|
||||
Reference in New Issue
Block a user