forked from yvv/decision
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:
@@ -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");
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
|
||||
@@ -564,6 +564,7 @@ impl Remote {
|
||||
author_name: "libreDecision".into(),
|
||||
author_email: "decision@example.org".into(),
|
||||
without_articles,
|
||||
simulated: false,
|
||||
});
|
||||
cfg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user