v3 : publication robuste et visible dans trustinWallet

- Clé de publication ed25519 générée au démarrage dans le volume ; sa partie
  publique et l'état de la file d'envoi s'affichent dans /acces.
- Publication initiale à l'instanciation d'un document seedé publiable.
- Reprise au démarrage : chaque document publié repasse une fois dans la file ;
  la date publiée est celle du dernier changement, un document inchangé ne
  fait pas de commit (testé contre un dépôt nu).
- Le travailleur fait un premier passage sans attendre ; journaux sans codes
  couleur hors terminal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 03:50:22 +02:00
co-authored by Claude Opus 5.5
parent 7a022dd35e
commit c2669a3d0b
10 changed files with 210 additions and 15 deletions
+67 -1
View File
@@ -151,7 +151,11 @@ async fn adoption_in_the_test_group_and_publication_queued_only_where_published(
assert_eq!(e3["inForce"]["id"], p.as_str());
assert!(e3["inForce"]["enteredAt"].is_string());
// The test group publishes nothing.
let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM outbox").fetch_one(&state.db).await.expect("count");
let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM outbox WHERE doc_id = ?")
.bind(&id)
.fetch_one(&state.db)
.await
.expect("count");
assert_eq!(n, 0);
}
@@ -386,3 +390,65 @@ async fn feedback_reaches_administrators() {
let (_, v) = admin.call("GET", "/api/access/feedback", None).await;
assert_eq!(v["feedback"][0]["body"], "Le bouton est trop discret.");
}
#[tokio::test]
async fn publication_writes_the_texts_in_force_to_the_repository() {
use std::process::Command;
let dir = std::env::temp_dir().join(format!("ld-pub-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).expect("dir");
let remote = dir.join("textes.git");
assert!(Command::new("git").args(["init", "-q", "--bare"]).arg(&remote).status().expect("git").success());
let mut cfg = Config::for_tests();
cfg.data_dir = dir.join("data");
cfg.publish = Some(ld_server::config::Publish {
remote: remote.display().to_string(),
branch: "main".into(),
key: dir.join("data/keys/publish_ed25519"),
author_name: "libreDecision".into(),
author_email: "decision@example.org".into(),
});
let db = open_db("sqlite::memory:").await.expect("db");
let state = AppState::new(cfg, db).await.expect("state");
seed::ensure(&state).await.expect("seed");
ld_server::publish::pass(&state).await.expect("pass");
let show = |path: &str| {
let out = Command::new("git")
.arg("-C")
.arg(&remote)
.args(["show", &format!("main:{path}")])
.output()
.expect("git show");
String::from_utf8(out.stdout).expect("utf8")
};
let fr = show("monnaie-libre/acte-engagement-certification/fr.md");
assert!(fr.contains("# Acte d'engagement Certification"), "{fr}");
assert!(fr.contains("## Préambule") && fr.contains("## Unicité du compte") && fr.contains("## Réciprocité"));
assert!(!fr.contains("Connaissance suffisante"), "only the texts in force are published");
assert!(show("monnaie-libre/acte-engagement-certification/en.md").contains("traduction: editoriale"));
let json: Value =
serde_json::from_str(&show("monnaie-libre/acte-engagement-certification/document.json")).expect("json");
assert_eq!(json["units"].as_array().expect("units").len(), 3);
assert_eq!(json["units"][0]["status"], "seed-minimal");
assert!(show("README.md").contains("libreDecision"));
let done: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM outbox WHERE done_at IS NULL")
.fetch_one(&state.db)
.await
.expect("count");
assert_eq!(done, 0);
// A restart queues the document again; nothing changed, so nothing is committed.
let head = || {
let out = Command::new("git").arg("-C").arg(&remote).args(["rev-parse", "main"]).output().expect("rev-parse");
String::from_utf8(out.stdout).expect("utf8")
};
let before = head();
ld_server::publish::resync(&state).await.expect("resync");
ld_server::publish::pass(&state).await.expect("pass");
assert_eq!(head(), before, "an unchanged document produces no commit");
let pending: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM outbox WHERE done_at IS NULL")
.fetch_one(&state.db)
.await
.expect("count");
assert_eq!(pending, 0);
let _ = std::fs::remove_dir_all(&dir);
}