v3 : publication — un envoi refusé repart, un dépôt neuf reçoit l'historique
Douzième relecture (850f9c4 : ok), défauts anciens de publish.rs mis en
jeu par le nouveau dépôt :
- un commit que le dépôt n'a jamais reçu (premier envoi refusé, clé sans
droit d'écriture) repart au passage suivant, au lieu d'une file marquée
faite avec un dépôt vide ;
- un changement de remote vers un dépôt neuf et vide lui envoie
l'historique ;
- au démarrage, ce qui attendait repart aussitôt (le délai croissant
pouvait atteindre 6 h).
trustinWallet montre l'état de la publication (en attente, dernier échec,
dernier commit) et offre « Publier maintenant ».
Boucle : 69 tests Rust, svelte-check, 9 vitest, e2e 13/13.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -463,6 +463,11 @@ async fn access_management_is_for_administrators_only() {
|
||||
assert_eq!(s, StatusCode::OK);
|
||||
let (_, ov) = admin.call("GET", "/api/access", None).await;
|
||||
assert!(ov["identities"].as_array().expect("i").iter().any(|i| i["displayName"] == "Chiara"));
|
||||
// « Publier maintenant » is for administrators, and needs a configured publication.
|
||||
let (s, _) = user.call("POST", "/api/access/publication/retry", None).await;
|
||||
assert_eq!(s, StatusCode::FORBIDDEN);
|
||||
let (s, v) = admin.call("POST", "/api/access/publication/retry", None).await;
|
||||
assert_eq!((s, v["error"].as_str()), (StatusCode::CONFLICT, Some("publication_off")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -682,3 +687,77 @@ async fn publication_writes_the_texts_in_force_to_the_repository() {
|
||||
assert_eq!(pending, 0);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_refused_first_push_goes_out_again_and_a_new_empty_repository_receives_it() {
|
||||
// Tenth independent review (850f9c4), 2026-10-21: a first push refused (a key
|
||||
// without write access) left the commit in the server's clone, and the next pass,
|
||||
// finding nothing to commit, marked the queue done with the repository still empty.
|
||||
use std::process::Command;
|
||||
let dir = std::env::temp_dir().join(format!("ld-pub-retry-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).expect("dir");
|
||||
let bare = |name: &str| {
|
||||
let path = dir.join(name);
|
||||
assert!(Command::new("git").args(["init", "-q", "--bare"]).arg(&path).status().expect("git").success());
|
||||
path
|
||||
};
|
||||
let config = |remote: &std::path::Path| {
|
||||
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(),
|
||||
});
|
||||
cfg
|
||||
};
|
||||
let has_main = |remote: &std::path::Path| {
|
||||
Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(remote)
|
||||
.args(["rev-parse", "--verify", "-q", "main"])
|
||||
.output()
|
||||
.expect("git")
|
||||
.status
|
||||
.success()
|
||||
};
|
||||
let pending = |db: &sqlx::SqlitePool| {
|
||||
let db = db.clone();
|
||||
async move {
|
||||
sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM outbox WHERE done_at IS NULL")
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.expect("count")
|
||||
}
|
||||
};
|
||||
|
||||
let first = bare("first.git");
|
||||
let hook = first.join("hooks/pre-receive");
|
||||
std::fs::write(&hook, "#!/bin/sh\nexit 1\n").expect("hook");
|
||||
assert!(Command::new("chmod").arg("755").arg(&hook).status().expect("chmod").success());
|
||||
let db = open_db("sqlite::memory:").await.expect("db");
|
||||
let state = AppState::new(config(&first), db.clone()).await.expect("state");
|
||||
seed::ensure(&state).await.expect("seed");
|
||||
ld_server::publish::pass(&state).await.expect("pass");
|
||||
assert!(!has_main(&first), "the push was refused");
|
||||
assert_eq!(pending(&db).await, 1, "the document still waits");
|
||||
|
||||
// The key gets its write access: the next start sends what waited, at once.
|
||||
std::fs::remove_file(&hook).expect("unhook");
|
||||
ld_server::publish::resync(&state).await.expect("resync");
|
||||
ld_server::publish::pass(&state).await.expect("pass");
|
||||
assert!(has_main(&first), "the commit kept in the clone went out");
|
||||
assert_eq!(pending(&db).await, 0);
|
||||
|
||||
// A change of remote to a new, empty repository: it receives the history.
|
||||
let second = bare("second.git");
|
||||
let moved = AppState::new(config(&second), db.clone()).await.expect("state");
|
||||
ld_server::publish::resync(&moved).await.expect("resync");
|
||||
ld_server::publish::pass(&moved).await.expect("pass");
|
||||
assert!(has_main(&second), "the new repository received the texts");
|
||||
assert_eq!(pending(&db).await, 0);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user