WIP lot S : floor_amend retiré (le quorum le remplace), fixture de production sous tests/fixtures/seed-production-2026-11-02/, reprise des tests (api, inertia, ballots, objections, typos)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
//! Inertia in five levels (ADR 0007): the levels and the scale served, the
|
||||
//! usual participation of a group, the change of inertia voted at the level
|
||||
//! of its article, the migration of the four words of before, and the
|
||||
//! amendment that raises E1 and E2 to level 4 in an existing base.
|
||||
//! amendment that raised E1 and E2 to level 4 in an existing base (30/10; since
|
||||
//! 2026-11-02 the seed puts them forward at a low level, ADR 0011: the scenarios
|
||||
//! of 30/10 are replayed on the seed production ran then).
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode, header};
|
||||
@@ -90,6 +92,14 @@ fn level(k: u8) -> InertiaLevel {
|
||||
InertiaLevel::new(k).expect("1 to 5")
|
||||
}
|
||||
|
||||
/// The level seed proposals start at (ADR 0011).
|
||||
const LOW: u8 = ld_server::seed::SEED_PROPOSAL_LEVEL;
|
||||
|
||||
/// The seed production runs on 2026-11-02 (3f10a0e): I1, E1, E2 in force, E1 and E2 at 4.
|
||||
fn production_seed_dir() -> std::path::PathBuf {
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/seed-production-2026-11-02")
|
||||
}
|
||||
|
||||
fn numbers(levels: &Value, key: &str) -> Vec<f64> {
|
||||
levels.as_array().expect("levels").iter().map(|l| l[key].as_f64().expect(key)).collect()
|
||||
}
|
||||
@@ -99,16 +109,16 @@ async fn the_five_levels_and_the_scale_are_served() {
|
||||
let (mut c, _) = Client::new().await;
|
||||
c.login("Chiara").await;
|
||||
let doc = c.doc("monnaie-libre").await;
|
||||
// 3 everywhere, 4 for the two structural commitments.
|
||||
let raised: Vec<&str> = doc["units"]
|
||||
// 3 everywhere, low for the three seed proposals (ADR 0011).
|
||||
let other: Vec<&str> = doc["units"]
|
||||
.as_array()
|
||||
.expect("units")
|
||||
.iter()
|
||||
.filter(|u| u["inertia"] != 3)
|
||||
.map(|u| u["code"].as_str().expect("code"))
|
||||
.collect();
|
||||
assert_eq!(raised, ["E1", "E2"]);
|
||||
assert!(unit(&doc, "E1")["inertia"] == 4 && unit(&doc, "E2")["inertia"] == 4);
|
||||
assert_eq!(other, ["I1", "E1", "E2"]);
|
||||
assert!(["I1", "E1", "E2"].iter().all(|c| unit(&doc, c)["inertia"] == LOW));
|
||||
|
||||
// No vote yet: the reference, 2C/W = 100 voters out of 5745.
|
||||
let p = &doc["doc"]["participation"];
|
||||
@@ -138,9 +148,9 @@ async fn the_five_levels_and_the_scale_are_served() {
|
||||
}
|
||||
|
||||
// A version: the five levels at its own participation, and the level it is voted at.
|
||||
let e1 = unit(&doc, "E1")["inForce"]["id"].as_str().expect("e1").to_owned();
|
||||
let e1 = unit(&doc, "E1")["proposals"][0]["id"].as_str().expect("e1").to_owned();
|
||||
let d = c.get(&format!("/api/proposals/{e1}")).await;
|
||||
assert_eq!(d["proposal"]["votedAtInertia"], 4);
|
||||
assert_eq!(d["proposal"]["votedAtInertia"], LOW);
|
||||
assert_eq!(numbers(&d["levels"], "cast"), [0.0; 5]);
|
||||
assert_eq!(numbers(&d["levels"], "required"), [50.0; 5], "nothing passes under C");
|
||||
assert_eq!(d["participation"]["source"], "reference");
|
||||
@@ -160,25 +170,28 @@ async fn a_change_of_inertia_is_voted_at_the_level_of_its_article_and_journaled(
|
||||
let (s, v) = a.call("POST", &format!("/api/units/{e2}/inertia"), Some(to(bad.clone()))).await;
|
||||
assert_eq!((s, v["error"].as_str()), (StatusCode::BAD_REQUEST, Some("inertia")), "{bad}");
|
||||
}
|
||||
let (s, v) = a.call("POST", &format!("/api/units/{e2}/inertia"), Some(to(json!(4)))).await;
|
||||
let (s, v) = a.call("POST", &format!("/api/units/{e2}/inertia"), Some(to(json!(LOW)))).await;
|
||||
assert_eq!((s, v["error"].as_str()), (StatusCode::BAD_REQUEST, Some("same_inertia")));
|
||||
let (s, v) = a.call("POST", &format!("/api/units/{e2}/inertia"), Some(to(json!(1)))).await;
|
||||
let (s, v) = a.call("POST", &format!("/api/units/{e2}/inertia"), Some(to(json!(5)))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{v}");
|
||||
let p = v["id"].as_str().expect("id").to_owned();
|
||||
let d = a.get(&format!("/api/proposals/{p}")).await;
|
||||
assert_eq!((d["proposal"]["inertiaTarget"].as_u64(), d["proposal"]["votedAtInertia"].as_u64()), (Some(1), Some(4)));
|
||||
// W(essai) = 2: both vote for, the article goes to level 1.
|
||||
assert_eq!(
|
||||
(d["proposal"]["inertiaTarget"].as_u64(), d["proposal"]["votedAtInertia"].as_u64()),
|
||||
(Some(5), Some(u64::from(LOW)))
|
||||
);
|
||||
// W(essai) = 2: both vote for, the article goes to level 5.
|
||||
a.vote(&p, "for").await;
|
||||
let v = b.vote(&p, "for").await;
|
||||
assert_eq!(v["entered"][0]["kind"], "inertia");
|
||||
let doc = a.doc("essai").await;
|
||||
let e2u = unit(&doc, "E2");
|
||||
assert_eq!(e2u["inertia"], 1);
|
||||
assert_eq!(e2u["inertia"], 5);
|
||||
let changes = e2u["inertiaChanges"].as_array().expect("changes");
|
||||
assert_eq!(changes.len(), 1);
|
||||
assert_eq!(
|
||||
(changes[0]["from"].as_u64(), changes[0]["to"].as_u64(), changes[0]["cause"].as_str()),
|
||||
(Some(4), Some(1), Some("vote"))
|
||||
(Some(u64::from(LOW)), Some(5), Some("vote"))
|
||||
);
|
||||
// The adoption keeps the level it was judged at, and the scale.
|
||||
let params: String = sqlx::query_scalar("SELECT params FROM adoptions WHERE proposal_id = ?")
|
||||
@@ -187,15 +200,16 @@ async fn a_change_of_inertia_is_voted_at_the_level_of_its_article_and_journaled(
|
||||
.await
|
||||
.expect("adoption");
|
||||
let params: Value = serde_json::from_str(¶ms).expect("json");
|
||||
let judged = Scale { electorate: 2, floor: 1.0, participation: 0.5 }.params(level(LOW));
|
||||
assert_eq!(
|
||||
(params["level"].as_u64(), params["majority_pct"].as_f64(), params["share_pct"].as_f64()),
|
||||
(Some(4), Some(60.0), Some(83.0))
|
||||
(Some(u64::from(LOW)), Some(judged.majority_pct), Some(level(LOW).share_pct()))
|
||||
);
|
||||
assert_eq!(
|
||||
(params["participation"].as_f64(), params["participation_source"].as_str()),
|
||||
(Some(0.5), Some("reference"))
|
||||
);
|
||||
assert!(params["gradient"].as_f64().is_some_and(|g| (g - gradient(level(4), 0.5)).abs() < 1e-12));
|
||||
assert!(params["gradient"].as_f64().is_some_and(|g| (g - gradient(level(LOW), 0.5)).abs() < 1e-12));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -358,9 +372,9 @@ async fn the_usual_participation_counts_the_statuses_it_names_and_only_those() {
|
||||
// The base as production holds it on 2026-10-30, then the new version
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// The seed as it was then: no modules yet (ADR 0009 came after).
|
||||
/// The seed as it was then: no modules yet (ADR 0009 came after), I1, E1 and E2 in force.
|
||||
fn seed_of_the_time(tag: &str) -> std::path::PathBuf {
|
||||
let today = Config::for_tests().seed_dir.join("acte-engagement-certification.json");
|
||||
let today = production_seed_dir().join("acte-engagement-certification.json");
|
||||
let mut seed: Value = serde_json::from_str(&std::fs::read_to_string(today).expect("seed")).expect("json");
|
||||
seed.as_object_mut().expect("object").remove("modules");
|
||||
let dir = std::env::temp_dir().join(format!("ld-levels-seed-{tag}-{}", std::process::id()));
|
||||
@@ -397,8 +411,11 @@ async fn start_new_version(pool: &SqlitePool) -> AppState {
|
||||
restart(pool).await
|
||||
}
|
||||
|
||||
/// Started again with the seed production ran after 30/10 (E1 and E2 at 4).
|
||||
async fn restart(pool: &SqlitePool) -> AppState {
|
||||
let state = AppState::new(Config::for_tests(), pool.clone()).await.expect("state");
|
||||
let mut cfg = Config::for_tests();
|
||||
cfg.seed_dir = production_seed_dir();
|
||||
let state = AppState::new(cfg, pool.clone()).await.expect("state");
|
||||
seed::ensure(&state).await.expect("seed");
|
||||
state
|
||||
}
|
||||
@@ -449,11 +466,26 @@ fn decided(rows: &[Decision]) -> Vec<(&str, &str, &str, Option<&str>)> {
|
||||
rows.iter().map(|(g, u, o, r)| (g.as_str(), u.as_str(), o.as_str(), r.as_deref())).collect()
|
||||
}
|
||||
|
||||
/// Since 2026-11-02 the seed gives E1 and E2 the low level of seed proposals: the
|
||||
/// raise of 30/10 is superseded, decided in production and nowhere else.
|
||||
#[tokio::test]
|
||||
async fn a_new_base_starts_with_e1_and_e2_at_level_4() {
|
||||
let (_, state) = Client::new().await;
|
||||
async fn a_new_base_starts_low_and_the_raise_of_30_10_decides_nothing() {
|
||||
let (mut c, state) = Client::new().await;
|
||||
assert_eq!(decided(&inertia_decisions(&state.db).await), []);
|
||||
let changes: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM inertia_changes").fetch_one(&state.db).await.expect("n");
|
||||
assert_eq!(changes, 0, "born low: nothing changed");
|
||||
c.login("Chiara").await;
|
||||
let doc = c.doc("monnaie-libre").await;
|
||||
assert!(["E1", "E2"].iter().all(|code| unit(&doc, code)["inertia"] == LOW));
|
||||
// On the seed of production, the raise is still there to decide: present at birth.
|
||||
let mut cfg = Config::for_tests();
|
||||
cfg.seed_dir = production_seed_dir();
|
||||
let db = open_db("sqlite::memory:").await.expect("db");
|
||||
let born = AppState::new(cfg, db).await.expect("state");
|
||||
seed::ensure(&born).await.expect("seed");
|
||||
assert_eq!(
|
||||
decided(&inertia_decisions(&state.db).await),
|
||||
decided(&inertia_decisions(&born.db).await),
|
||||
[
|
||||
("essai", "E1", "present", None),
|
||||
("essai", "E2", "present", None),
|
||||
@@ -461,9 +493,6 @@ async fn a_new_base_starts_with_e1_and_e2_at_level_4() {
|
||||
("monnaie-libre", "E2", "present", None),
|
||||
]
|
||||
);
|
||||
let changes: i64 =
|
||||
sqlx::query_scalar("SELECT COUNT(*) FROM inertia_changes").fetch_one(&state.db).await.expect("n");
|
||||
assert_eq!(changes, 0, "born at 4: nothing changed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user