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:
@@ -4,6 +4,10 @@
|
||||
//! base is born with them open; an existing base gets them through a guarded
|
||||
//! amendment, only where nobody has touched the text. Monnaie libre publishes no
|
||||
//! document without any article in force: the queue waits for the first text.
|
||||
//!
|
||||
//! Lot T4 (2026-11-03): a fourth, E12 « Responsabilité civique », and Yvv's texts for the three
|
||||
//! (`tests/amorce.rs`). A new base is born with the four; the bases made as production are
|
||||
//! started here with the seed of M8, the last that gave the three their texts of before.
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode, header};
|
||||
@@ -107,7 +111,7 @@ async fn fresh_with(cfg: Config) -> AppState {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_new_base_is_born_with_three_seed_proposals_and_no_article_in_force() {
|
||||
async fn a_new_base_is_born_with_four_seed_proposals_and_no_article_in_force() {
|
||||
let state = fresh().await;
|
||||
let mut c = Client::of(&state);
|
||||
c.login("Chiara", false).await;
|
||||
@@ -121,7 +125,8 @@ async fn a_new_base_is_born_with_three_seed_proposals_and_no_article_in_force()
|
||||
.filter_map(|u| u["code"].as_str())
|
||||
.collect();
|
||||
assert!(in_force.is_empty(), "{group}: no article in force, {in_force:?}");
|
||||
for code in CODES {
|
||||
for code in CODES.iter().chain(&["E12"]) {
|
||||
let code = *code;
|
||||
let u = unit(&doc, code);
|
||||
let p = &u["proposals"][0];
|
||||
assert_eq!(
|
||||
@@ -135,19 +140,20 @@ async fn a_new_base_is_born_with_three_seed_proposals_and_no_article_in_force()
|
||||
assert!(p["translations"]["en"]["title"].is_string(), "{group} {code}: translated");
|
||||
}
|
||||
assert_eq!(unit(&doc, "I1")["proposals"][0]["title"], "Raison d'être");
|
||||
// Only those three; the title and the orders stay seeds a minima, in force.
|
||||
// Only those four; the title and the orders stay seeds a minima, in force.
|
||||
let marked: Vec<&Value> = versions(&doc).into_iter().filter(|v| v["seedProposal"] == true).collect();
|
||||
assert_eq!(marked.len(), 3, "{group}");
|
||||
assert_eq!(marked.len(), 4, "{group}");
|
||||
let title = &doc["modules"]["title"]["inForce"];
|
||||
assert_eq!((title["seedMinimal"].as_bool(), title["seedProposal"].as_bool()), (Some(true), Some(false)));
|
||||
assert!(
|
||||
doc["modules"]["orders"].as_array().expect("orders").iter().all(|o| o["inForce"]["seedMinimal"] == true)
|
||||
);
|
||||
// The counts: no article in force, three more open versions; still 41 modules to vote.
|
||||
// The counts: no article in force, 29 open versions (27 articles, E3 and E7 two each);
|
||||
// 33 modules to vote (27 articles, the title, the order of 5 sections; lot T4).
|
||||
let g = c.get(&format!("/api/groups/{group}")).await;
|
||||
assert_eq!((g["docs"][0]["inForce"].as_u64(), g["docs"][0]["open"].as_u64()), (Some(0), Some(35)), "{group}");
|
||||
assert_eq!((g["docs"][0]["inForce"].as_u64(), g["docs"][0]["open"].as_u64()), (Some(0), Some(29)), "{group}");
|
||||
let d = c.get(&format!("/api/groups/{group}/decisions")).await;
|
||||
assert_eq!(d["docs"][0]["toVote"], 41, "{group}");
|
||||
assert_eq!(d["docs"][0]["toVote"], 33, "{group}");
|
||||
// The preview of the text in force is empty; « if everything passes » shows them.
|
||||
let id = doc["doc"]["id"].as_str().expect("id");
|
||||
let p = c.get(&format!("/api/docs/{id}/preview")).await;
|
||||
@@ -155,7 +161,8 @@ async fn a_new_base_is_born_with_three_seed_proposals_and_no_article_in_force()
|
||||
let all = c.get(&format!("/api/docs/{id}/preview?mode=all")).await;
|
||||
assert!(all["units"].as_array().expect("units").iter().any(|u| u["title"] == "Raison d'être"));
|
||||
}
|
||||
// Born with them: nothing to amend, and it is said once per article and group.
|
||||
// Born with them: the seed proposal amendments, superseded since lot T4 (the seed puts
|
||||
// forward Yvv's texts, not theirs), decide nothing; the texts of lot T4 are said present.
|
||||
let decided: Vec<(String, String, String)> = sqlx::query_as(
|
||||
"SELECT d.group_slug, s.unit_code, s.outcome FROM seed_proposal_amendments s
|
||||
JOIN documents d ON d.id = s.doc_id ORDER BY d.group_slug, s.unit_code",
|
||||
@@ -163,14 +170,13 @@ async fn a_new_base_is_born_with_three_seed_proposals_and_no_article_in_force()
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.expect("decisions");
|
||||
assert_eq!(decided.len(), 6, "{decided:?}");
|
||||
assert!(decided.iter().all(|(_, _, o)| o == "present"), "{decided:?}");
|
||||
assert!(decided.is_empty(), "{decided:?}");
|
||||
let changes: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM inertia_changes").fetch_one(&state.db).await.expect("changes");
|
||||
assert_eq!(changes, 0, "born at the low level: nothing changed");
|
||||
// The Raison d'être of a new base is still the text its own amendment names.
|
||||
// The Raison d'être of a new base is the text its own amendment of lot T4 names.
|
||||
let i1: Vec<String> = sqlx::query_scalar(
|
||||
"SELECT outcome FROM seed_amendments WHERE amendment_id = '2026-10-30-i1-raison-d-etre' ORDER BY doc_id",
|
||||
"SELECT outcome FROM seed_amendments WHERE amendment_id = '2026-11-03-i1-amorce' ORDER BY doc_id",
|
||||
)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
@@ -242,7 +248,16 @@ async fn production(tag: &str, cfg: Config) -> AppState {
|
||||
state
|
||||
}
|
||||
|
||||
/// The same base, started again with today's seed (and its amendments).
|
||||
/// The seed of M6 for these three texts, unchanged until M8 (de1b7eb): since lot T4 (03/11) the
|
||||
/// seed of today gives them Yvv's texts, and their seed proposal amendments are superseded
|
||||
/// (`tests/amorce.rs`).
|
||||
fn m8() -> Config {
|
||||
let mut cfg = Config::for_tests();
|
||||
cfg.seed_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/seed-m8");
|
||||
cfg
|
||||
}
|
||||
|
||||
/// The same base, started again with a later seed (and its amendments).
|
||||
async fn restart(before: &AppState, cfg: Config) -> AppState {
|
||||
let state = AppState::new(cfg, before.db.clone()).await.expect("state");
|
||||
seed::ensure(&state).await.expect("seed");
|
||||
@@ -367,7 +382,7 @@ async fn the_production_base_turns_its_untouched_texts_into_seed_proposals_once(
|
||||
};
|
||||
let translated = translations(before.clone()).await;
|
||||
|
||||
let state = restart(&before, Config::for_tests()).await;
|
||||
let state = restart(&before, m8()).await;
|
||||
let first = decisions(&state).await;
|
||||
// Every text becomes a seed proposal; the level goes low where nobody touched it:
|
||||
// an open change of inertia by a member keeps it (I1 and E1 in Monnaie libre).
|
||||
@@ -480,7 +495,7 @@ async fn the_production_base_turns_its_untouched_texts_into_seed_proposals_once(
|
||||
);
|
||||
|
||||
// Decided once: a second start changes nothing.
|
||||
let again = restart(&state, Config::for_tests()).await;
|
||||
let again = restart(&state, m8()).await;
|
||||
assert_eq!(decisions(&again).await, first);
|
||||
for group in GROUPS {
|
||||
for code in CODES {
|
||||
@@ -508,7 +523,7 @@ async fn a_vote_keeps_the_text_in_force_and_the_refusal_is_kept() {
|
||||
let v = c.vote(&e1.id, "against").await;
|
||||
assert!(v["entered"].as_array().expect("entered").is_empty());
|
||||
|
||||
let state = restart(&before, Config::for_tests()).await;
|
||||
let state = restart(&before, m8()).await;
|
||||
let first = decisions(&state).await;
|
||||
assert_eq!(
|
||||
outcomes(&first),
|
||||
@@ -533,7 +548,7 @@ async fn a_vote_keeps_the_text_in_force_and_the_refusal_is_kept() {
|
||||
// Decided once: the vote withdrawn, a later start does not look again.
|
||||
let (s, v) = c.call("POST", &format!("/api/proposals/{}/vote", kept.id), Some(json!({ "choice": null }))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{v}");
|
||||
let again = restart(&state, Config::for_tests()).await;
|
||||
let again = restart(&state, m8()).await;
|
||||
assert_eq!(decisions(&again).await, first);
|
||||
assert!(version(&again, "essai", "E1").await.in_force);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user