Lot V : « Pas avant des votes prouvés » (Yvv, 03/11) — un texte entré par des votes simulés est en vigueur sur le site, la publication attend que tout ce qui est en vigueur tienne sur les seuls votes prouvés (publish::waits_for_proven_votes, simulation::unproven), LD_PUBLISH_SIMULATED=1 publie quand même ; publication.waitsFor ; vu rouge puis vert

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-11-03 05:42:13 +01:00
co-authored by Claude Opus 5.5
parent 0cde5bdbd0
commit 9b7345346e
7 changed files with 258 additions and 25 deletions
+3
View File
@@ -798,6 +798,7 @@ async fn publication_writes_the_texts_in_force_to_the_repository() {
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
without_articles: false,
simulated: false,
});
let db = open_db("sqlite::memory:").await.expect("db");
let state = AppState::new(cfg, db).await.expect("state");
@@ -872,6 +873,7 @@ async fn a_refused_first_push_goes_out_again_and_a_new_empty_repository_receives
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
without_articles: false,
simulated: false,
});
cfg
};
@@ -1067,6 +1069,7 @@ async fn a_seed_amendment_replaces_an_untouched_preambule_once_and_republishes_i
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
without_articles: false,
simulated: false,
});
let git = |args: &[&str]| {
let out = Command::new("git").arg("-C").arg(&remote).args(args).output().expect("git");
+124
View File
@@ -2075,3 +2075,127 @@ async fn a_vote_exists_for_every_state_of_the_buffer() {
assert!(exists(unit_id("E1")).await, "a proven trace stays");
let _ = doc_id;
}
/// A bare repository of the texts, for the publication (ADR 0011, 0012).
struct Texts {
dir: std::path::PathBuf,
bare: std::path::PathBuf,
}
impl Texts {
fn new(name: &str) -> Self {
let dir = std::env::temp_dir().join(format!("ld-v-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("dir");
let bare = dir.join("textes.git");
let ok = std::process::Command::new("git").args(["init", "-q", "--bare"]).arg(&bare).status().expect("git");
assert!(ok.success());
Self { dir, bare }
}
/// A configuration that publishes there; `simulated`: `LD_PUBLISH_SIMULATED`.
fn config(&self, mut cfg: Config, simulated: bool) -> Config {
cfg.data_dir = self.dir.join("data");
cfg.publish = Some(ld_server::config::Publish {
remote: self.bare.display().to_string(),
branch: "main".into(),
key: self.dir.join("data/keys/publish_ed25519"),
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
without_articles: false,
simulated,
});
cfg
}
/// The French text of the Acte as the repository has it; `None`: nothing published.
fn french(&self) -> Option<String> {
let out = std::process::Command::new("git")
.arg("-C")
.arg(&self.bare)
.args(["show", "main:monnaie-libre/acte-engagement-certification/fr.md"])
.output()
.expect("git show");
out.status.success().then(|| String::from_utf8(out.stdout).expect("utf8"))
}
}
impl Drop for Texts {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
/// A body of a version, as the document shows it.
fn body_of(doc: &Value, id: &str) -> String {
find(doc, id)["body"].as_str().expect("body").to_owned()
}
/// Yvv, 2026-11-03: « Pas avant des votes prouvés ». A text that enters by simulated
/// votes is in force on the site, but sends nothing to the repository of the texts:
/// the publication waits until everything in effect holds on the proven votes alone —
/// the re-examination of the act « supprime » (`simulation`), told only.
#[tokio::test]
async fn a_text_entered_by_simulated_votes_is_in_force_but_never_published_before_proven_votes() {
let indexer = Indexer::start().await;
let texts = Texts::new("proven");
let (mut c, state) = Client::new(texts.config(chain_config(&indexer), false)).await;
c.login("Chiara").await;
let doc = c.doc("monnaie-libre").await;
let doc_id = doc["doc"]["id"].as_str().expect("id").to_owned();
small_web(&state, &doc_id).await;
let (e4, e5) = (version(&doc, "E4", "0"), version(&doc, "E5", "0"));
// The chain closed: two simulated « pour », E4 enters — in force on the site.
c.ok("POST", "/api/dev/vote-chain", Some(json!({ "open": false }))).await;
for name in ["Luna", "Bruno"] {
let mut s = c.other();
s.login(name).await;
s.ok("POST", &format!("/api/proposals/{e4}/vote"), Some(json!({ "choice": "for" }))).await;
}
assert_eq!(unit(&c.doc("monnaie-libre").await, "E4")["inForce"]["id"], e4.as_str());
ld_server::publish::pass(&state).await.expect("pass");
assert!(texts.french().is_none(), "not before proven votes");
let publication = &c.doc("monnaie-libre").await["doc"]["publication"];
assert_eq!((publication["state"].as_str(), publication["waitsFor"].as_str()), (Some("suspended"), Some("proven")));
// The chain open: E5 enters on two proven votes — E4 still does not hold on them: nothing goes out.
c.ok("POST", "/api/dev/vote-chain", Some(json!({ "open": true }))).await;
let charlie = Signer { account: CHARLIE, identity: Some((9003, "Charlie", true)) };
for (block, signer) in [(100, alice()), (101, charlie)] {
let b = c.ballot(&doc, &[(&e5, Some("for"))], None).await;
indexer.pay(block, comment_of(&b), signer);
}
ballots::sweep(&state).await.expect("sweep");
let d = c.doc("monnaie-libre").await;
assert_eq!(unit(&d, "E5")["inForce"]["id"], e5.as_str());
ld_server::publish::pass(&state).await.expect("pass");
assert!(texts.french().is_none(), "E4 does not hold on the proven votes yet");
// The act « supprime »: E4 returns among the proposals; what is in force holds on the
// proven votes alone — it goes out.
ld_server::simulation::discard(&state, true).await.expect("act");
ld_server::publish::pass(&state).await.expect("pass");
let fr = texts.french().expect("published");
let (b4, b5) = (body_of(&d, &e4), body_of(&d, &e5));
assert!(fr.contains(b5.lines().next().expect("line")), "{fr}");
assert!(!fr.contains(b4.lines().next().expect("line")), "{fr}");
assert_eq!(c.doc("monnaie-libre").await["doc"]["publication"]["state"], "done");
}
/// Yvv may choose the other way (`LD_PUBLISH_SIMULATED=1`): published anyway.
#[tokio::test]
async fn with_the_switch_a_text_entered_by_simulated_votes_is_published() {
let texts = Texts::new("switch");
let (mut c, state) = Client::new(texts.config(Config::for_tests(), true)).await;
c.login("Chiara").await;
let doc = c.doc("monnaie-libre").await;
let doc_id = doc["doc"]["id"].as_str().expect("id").to_owned();
small_web(&state, &doc_id).await;
let e4 = version(&doc, "E4", "0");
for name in ["Luna", "Bruno"] {
let mut s = c.other();
s.login(name).await;
s.ok("POST", &format!("/api/proposals/{e4}/vote"), Some(json!({ "choice": "for" }))).await;
}
ld_server::publish::pass(&state).await.expect("pass");
let fr = texts.french().expect("published");
assert!(fr.contains(body_of(&doc, &e4).lines().next().expect("line")), "{fr}");
}
+1
View File
@@ -564,6 +564,7 @@ impl Remote {
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
without_articles,
simulated: false,
});
cfg
}