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:
Yvv
2026-11-03 21:21:44 +01:00
co-authored by Claude Opus 5.5
parent dda1ca85dd
commit 093d1ca471
14 changed files with 384 additions and 189 deletions
+58 -43
View File
@@ -146,8 +146,9 @@ async fn a_new_base_has_its_title_and_the_order_of_each_section() {
c.login("Chiara").await;
for group in ["monnaie-libre", "essai"] {
let doc = c.doc(group).await;
// The articles stay the articles: 33 of them, none of the modules.
assert_eq!(doc["units"].as_array().expect("units").len(), 33, "{group}");
// The articles stay the articles: 27 of them, none of the modules (since lot T4, the
// rules of operation are out of the document, ADR 0014).
assert_eq!(doc["units"].as_array().expect("units").len(), 27, "{group}");
let title = &doc["modules"]["title"];
assert_eq!(
(title["kind"].as_str(), title["code"].as_str(), title["inertia"].as_u64()),
@@ -163,27 +164,15 @@ async fn a_new_base_has_its_title_and_the_order_of_each_section() {
// One order per section, in the order of the document, each at level 2.
let orders = doc["modules"]["orders"].as_array().expect("orders");
let sections: Vec<&str> = orders.iter().filter_map(|o| o["section"].as_str()).collect();
assert_eq!(
sections,
[
"introduction",
"fondamental",
"technique",
"conclusion",
"annexe",
"formule",
"inertie",
"ordonnancement"
]
);
assert_eq!(sections, ["introduction", "fondamental", "technique", "conclusion", "annexe"]);
assert!(orders.iter().all(|o| o["inertia"] == 2 && o["inForce"]["seedMinimal"] == true));
// Its version in force is the order the section reads in now.
let fondamental = order_of(&doc, "fondamental");
assert_eq!(codes(&doc, &fondamental["members"]), ["T1", "E1", "E2"]);
assert_eq!(codes(&doc, &fondamental["members"]), ["T1", "E1", "E12", "E2"]);
assert_eq!(fondamental["inForce"]["order"]["ids"], fondamental["members"]);
assert_eq!(fondamental["moot"], false);
// « Ordonnancement » holds one article: nothing to decide.
assert_eq!(order_of(&doc, "ordonnancement")["moot"], true);
// Every section holds two articles or more: each order has something to decide
// (one of a single article: `an_order_of_one_article_has_nothing_to_decide`).
assert!(orders.iter().all(|o| o["moot"] == false), "{group}");
}
// Born with them: the addition says so, once per document.
let rows: Vec<(String, String)> = sqlx::query_as(
@@ -197,11 +186,35 @@ async fn a_new_base_has_its_title_and_the_order_of_each_section() {
let doc = c.doc("monnaie-libre").await;
let id = doc["doc"]["id"].as_str().expect("id");
let all = c.ok("GET", &format!("/api/docs/{id}/preview?mode=all"), None).await;
assert_eq!(all["units"].as_array().expect("units").len(), 33);
// The list of the group: the articles in force — none, I1, E1 and E2 are seed proposals
// (ADR 0011), three more open versions —; no module has an open proposal yet.
assert_eq!(all["units"].as_array().expect("units").len(), 27);
// The list of the group: the articles in force — none, I1, E1, E12 and E2 are seed
// proposals (ADR 0011) —, one open version per article and two more (E3, E7); no module
// has an open proposal yet.
let g = c.ok("GET", "/api/groups/monnaie-libre", None).await;
assert_eq!((g["docs"][0]["inForce"].as_u64(), g["docs"][0]["open"].as_u64()), (Some(0), Some(35)));
assert_eq!((g["docs"][0]["inForce"].as_u64(), g["docs"][0]["open"].as_u64()), (Some(0), Some(29)));
}
/// The seed of M8 (tests/fixtures/seed-m8): « Ordonnancement » held one article, O1. Lot T4
/// takes that section out of the document (ADR 0014); a base born on that seed still has it.
fn m8_config() -> Config {
let mut cfg = Config::for_tests();
cfg.seed_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/seed-m8");
cfg
}
#[tokio::test]
async fn an_order_of_one_article_has_nothing_to_decide() {
let db = open_db("sqlite::memory:").await.expect("db");
let state = AppState::new(m8_config(), db).await.expect("state");
seed::ensure(&state).await.expect("seed");
let mut c = Client::of(&state);
c.login("Chiara").await;
for group in ["monnaie-libre", "essai"] {
let doc = c.doc(group).await;
assert_eq!(codes(&doc, &order_of(&doc, "ordonnancement")["members"]), ["O1"], "{group}");
assert_eq!(order_of(&doc, "ordonnancement")["moot"], true, "{group}");
assert_eq!(order_of(&doc, "conclusion")["moot"], false, "{group}");
}
}
#[tokio::test]
@@ -251,7 +264,7 @@ async fn an_order_is_proposed_with_the_articles_of_its_section_then_voted_into_f
assert_eq!((back["status"].as_str(), back["origin"].as_str()), (Some("open"), Some("seed")));
assert_eq!(codes(&doc, &back["order"]["ids"]), ["K1", "K2"]);
// Nothing else moved: the other sections read as before, the texts are the same.
assert_eq!(read_order(&doc, "fondamental"), ["T1", "E1", "E2"]);
assert_eq!(read_order(&doc, "fondamental"), ["T1", "E1", "E12", "E2"]);
assert_eq!(read_order(&doc, "technique")[0], "T2");
}
@@ -259,12 +272,12 @@ async fn an_order_is_proposed_with_the_articles_of_its_section_then_voted_into_f
async fn articles_that_enter_or_leave_a_section_complete_an_order_under_vote() {
let (mut yvv, mut chiara, state) = two().await;
// The order stays under vote with one voice.
two_voices(&state, "ordre.inertie").await;
two_voices(&state, "ordre.conclusion").await;
let doc = yvv.doc("essai").await;
let module = order_of(&doc, "inertie")["id"].as_str().expect("id").to_owned();
let module = order_of(&doc, "conclusion")["id"].as_str().expect("id").to_owned();
let doc_id = doc["doc"]["id"].as_str().expect("id").to_owned();
let (n1, n11) = (id_of(&doc, "N1"), id_of(&doc, "N1.1"));
let p = yvv.ok("POST", &format!("/api/units/{module}/order"), Some(json!({ "order": [n11, n1] }))).await["id"]
let (k1, k2) = (id_of(&doc, "K1"), id_of(&doc, "K2"));
let p = yvv.ok("POST", &format!("/api/units/{module}/order"), Some(json!({ "order": [k2, k1] }))).await["id"]
.as_str()
.expect("id")
.to_owned();
@@ -286,20 +299,22 @@ async fn articles_that_enter_or_leave_a_section_complete_an_order_under_vote() {
.ok(
"POST",
&format!("/api/docs/{doc_id}/proposals"),
Some(json!({ "section": "inertie", "title": "Inertie des modules", "body": "Le titre du document est à 4.", "submit": true })),
Some(
json!({ "section": "conclusion", "title": "Rappel", "body": "Un rappel pour finir.", "submit": true }),
),
)
.await;
let doc = yvv.doc("essai").await;
let fresh = unit(&doc, "N1")["code"]
let fresh = unit(&doc, "K1")["code"]
.as_str()
.map(|_| ())
.and_then(|()| doc["units"].as_array().expect("u").iter().find(|u| u["id"] == created["unitId"]).cloned());
let fresh = fresh.expect("the new article");
let new_code = fresh["code"].as_str().expect("code").to_owned();
assert_eq!(read_order(&doc, "inertie"), ["N1", "N1.1", new_code.as_str()], "a new article goes at the end");
let module_now = order_of(&doc, "inertie");
assert_eq!(read_order(&doc, "conclusion"), ["K1", "K2", new_code.as_str()], "a new article goes at the end");
let module_now = order_of(&doc, "conclusion");
let open = module_now["proposals"].as_array().expect("p").iter().find(|x| x["id"] == p.as_str()).expect("p");
assert_eq!(codes(&doc, &open["order"]["ids"]), ["N1.1", "N1", new_code.as_str()]);
assert_eq!(codes(&doc, &open["order"]["ids"]), ["K2", "K1", new_code.as_str()]);
assert_eq!(codes(&doc, &open["order"]["added"]), [new_code.as_str()]);
// Its vote stays: completing an order never asks the voters again.
assert_eq!((open["tally"]["for"].as_u64(), open["myVote"].as_str()), (Some(1), Some("for")));
@@ -307,47 +322,47 @@ async fn articles_that_enter_or_leave_a_section_complete_an_order_under_vote() {
let added = created["id"].as_str().expect("id");
chiara.ok("POST", &format!("/api/proposals/{added}/withdraw"), None).await;
let doc = yvv.doc("essai").await;
let open = order_of(&doc, "inertie")["proposals"]
let open = order_of(&doc, "conclusion")["proposals"]
.as_array()
.expect("p")
.iter()
.find(|x| x["id"] == p.as_str())
.cloned()
.expect("p");
assert_eq!(codes(&doc, &open["order"]["ids"]), ["N1.1", "N1"]);
assert_eq!(codes(&doc, &open["order"]["ids"]), ["K2", "K1"]);
assert!(open["order"]["added"].as_array().expect("a").is_empty());
// An order proposed with three articles, one of which then leaves: dropped from it.
let created = chiara
.ok(
"POST",
&format!("/api/docs/{doc_id}/proposals"),
Some(json!({ "section": "inertie", "title": "Autre", "body": "Un article de passage dans la section.", "submit": true })),
Some(json!({ "section": "conclusion", "title": "Autre", "body": "Un article de passage dans la section.", "submit": true })),
)
.await;
let other = created["unitId"].as_str().expect("unit").to_owned();
let three = yvv.ok("POST", &format!("/api/units/{module}/order"), Some(json!({ "order": [other, n11, n1] }))).await
let three = yvv.ok("POST", &format!("/api/units/{module}/order"), Some(json!({ "order": [other, k2, k1] }))).await
["id"]
.as_str()
.expect("id")
.to_owned();
chiara.ok("POST", &format!("/api/proposals/{}/withdraw", created["id"].as_str().expect("id")), None).await;
let doc = yvv.doc("essai").await;
let o = order_of(&doc, "inertie")["proposals"]
let o = order_of(&doc, "conclusion")["proposals"]
.as_array()
.expect("p")
.iter()
.find(|x| x["id"] == three.as_str())
.cloned()
.expect("o");
assert_eq!(codes(&doc, &o["order"]["ids"]), ["N1.1", "N1"]);
assert_eq!(codes(&doc, &o["order"]["ids"]), ["K2", "K1"]);
assert_eq!(o["order"]["dropped"], json!([other]));
// Completed, it reads like the first one: the second voice brings that order in.
let v = chiara.vote(&p, "for").await;
assert_eq!(v["entered"][0]["kind"], "order");
let doc = yvv.doc("essai").await;
assert_eq!(read_order(&doc, "inertie"), ["N1.1", "N1"]);
assert_eq!(read_order(&doc, "conclusion"), ["K2", "K1"]);
// Now the order in force: the other one changes nothing more and cannot enter.
let o = order_of(&doc, "inertie")["proposals"]
let o = order_of(&doc, "conclusion")["proposals"]
.as_array()
.expect("p")
.iter()
@@ -503,10 +518,10 @@ async fn a_new_article_goes_at_the_end_of_the_section_asked_for() {
.to_owned()
};
let (ca, cb) = (code(&a), code(&b));
assert_eq!(read_order(&doc, "fondamental"), ["T1", "E1", "E2", ca.as_str()]);
assert_eq!(read_order(&doc, "fondamental"), ["T1", "E1", "E12", "E2", ca.as_str()]);
assert_eq!(read_order(&doc, "introduction"), ["I1", "I2", cb.as_str()]);
// It joins the order of its section, at its end.
assert_eq!(codes(&doc, &order_of(&doc, "fondamental")["members"]), ["T1", "E1", "E2", ca.as_str()]);
assert_eq!(codes(&doc, &order_of(&doc, "fondamental")["members"]), ["T1", "E1", "E12", "E2", ca.as_str()]);
}
// ---------------------------------------------------------------------------