mod common;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use common::*;
use gunnar_ssh::{Access, EXIT_BUSY, STARTING_MESSAGE};
const UPLOAD: &str = "git-upload-pack 'nordisk/osm-katana.git'";
#[tokio::test]
async fn a_transfer_in_the_boot_window_is_told_to_retry_and_then_served() {
let key = mint_key();
let ready = Arc::new(AtomicBool::new(false));
let fixture = start_with_readiness(&key, Access::Admin, Arc::clone(&ready)).await;
let session = authenticate(fixture.server.local_addr(), &key, "git")
.await
.expect("connect")
.expect("authenticate");
let refused = exec(&session, UPLOAD).await.expect("exec");
let (stdout, status) = match refused {
ExecOutcome::Accepted {
stdout,
exit_status,
} => (stdout, exit_status),
other => panic!(
"the boot window must ANSWER, in git's protocol, not drop the channel: {other:?}"
),
};
assert!(
stdout.contains("starting"),
"the word `starting` is what a script keys on to tell `not yet` from `gone`: {stdout:?}"
);
assert!(
stdout.contains(STARTING_MESSAGE),
"the sentence is the constant, so the server and its documentation cannot drift: {stdout:?}"
);
assert!(
!stdout.contains("unavailable"),
"`unavailable` is what a client is told about a repository that is GONE. Saying it here \
is the defect that made the door report 64 present pushes as missing: {stdout:?}"
);
assert_eq!(
status,
Some(EXIT_BUSY),
"EX_TEMPFAIL (75), not 128. A retry script has to be able to tell a booting server from \
a permission error"
);
assert!(
fixture.transport.served().is_empty(),
"nothing may be SERVED in the boot window: the whole point is that the store is not \
ready to be read, so a request that reaches it is the bug"
);
ready.store(true, Ordering::Relaxed);
let served = exec(&session, UPLOAD).await.expect("exec");
match served {
ExecOutcome::Accepted { stdout, .. } => assert!(
stdout.contains("git-upload-pack"),
"a ready server serves: {stdout:?}"
),
other => panic!("the same command must be served once the flag is set: {other:?}"),
}
assert_eq!(
fixture.transport.served().len(),
1,
"exactly the one transfer that arrived after readiness"
);
}
#[tokio::test]
async fn a_listener_with_no_readiness_flag_serves_at_once() {
let key = mint_key();
let fixture = start_with_key(&key, Access::Admin).await;
let session = authenticate(fixture.server.local_addr(), &key, "git")
.await
.expect("connect")
.expect("authenticate");
match exec(&session, UPLOAD).await.expect("exec") {
ExecOutcome::Accepted { stdout, .. } => assert!(stdout.contains("git-upload-pack")),
other => panic!("a server with no boot window serves from its first socket: {other:?}"),
}
}
#[tokio::test]
async fn a_keyed_transfer_in_the_boot_window_waits_and_is_served() {
let key = mint_key();
let ready = Arc::new(AtomicBool::new(false));
let config = gunnar_ssh::SshConfig {
keyed_admission_wait: Some(std::time::Duration::from_secs(10)),
..gunnar_ssh::SshConfig::ephemeral_loopback()
};
let fixture =
common::start_with_readiness_and_config(&key, Access::Admin, Arc::clone(&ready), config)
.await;
let session = authenticate(fixture.server.local_addr(), &key, "git")
.await
.expect("connect")
.expect("authenticate");
let flag = Arc::clone(&ready);
let opener = tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(700)).await;
flag.store(true, Ordering::Relaxed);
});
let started = std::time::Instant::now();
match exec(&session, UPLOAD).await.expect("exec") {
ExecOutcome::Accepted { stdout, .. } => {
assert!(
!stdout.contains("starting"),
"a key was REFUSED in the boot window although it may wait 10 s and the stores \
opened after 0.7 s: {stdout:?}"
);
assert!(
stdout.contains("git-upload-pack"),
"served once ready: {stdout:?}"
);
}
other => panic!("the keyed exec must be served: {other:?}"),
}
assert!(
started.elapsed() >= std::time::Duration::from_millis(600),
"it waited for the flag"
);
assert_eq!(fixture.transport.served().len(), 1);
opener.await.unwrap();
}