Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
Each task's tests were run against a reference at its end state; the end states were replayed from master in order with the gate at each step (650 to 762 tests); each skeleton compiles against its tests and fails them. The reference is kept off this machine. Lessons T27 (every wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
//! The `gatewayd` program: its usage, what stops it at start, the warning for a secret in a file,
|
||||
//! and that the token never reaches its output (M4a spec, sections 3, 4 and 10). Do not edit.
|
||||
|
||||
#[path = "support/tmp.rs"]
|
||||
mod tmp;
|
||||
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use tmp::TempDir;
|
||||
|
||||
const KYLE: &str = "k0000000000000000000000000";
|
||||
const TOKEN: &str = "tok-3f9a1c7e5b2d4f6a8c0e";
|
||||
|
||||
fn gatewayd() -> Command {
|
||||
let mut c = Command::new(env!("CARGO_BIN_EXE_gatewayd"));
|
||||
c.env_remove("GW_TEST_TOKEN")
|
||||
.env_remove("CREDENTIALS_DIRECTORY");
|
||||
c
|
||||
}
|
||||
|
||||
fn closed_url() -> String {
|
||||
let port = std::net::TcpListener::bind("127.0.0.1:0")
|
||||
.unwrap()
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port();
|
||||
format!("http://127.0.0.1:{port}")
|
||||
}
|
||||
|
||||
fn write_config(dir: &TempDir, secret: &str) -> std::path::PathBuf {
|
||||
let text = format!(
|
||||
"[mattermost]\nurl = \"{}\"\n[secrets.mattermost_token]\n{secret}\n[allow]\nusers = [\"{KYLE}\"]\n[paths]\nhome = \"{}\"\n",
|
||||
closed_url(),
|
||||
dir.path().join("home").display()
|
||||
);
|
||||
dir.write("gatewayd.toml", &text)
|
||||
}
|
||||
|
||||
/// Run until `want` appears on standard error or 5 s pass, then kill it; all it printed.
|
||||
fn stderr_until(mut cmd: Command, want: &str) -> String {
|
||||
let mut child = cmd
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::null())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
let mut reader = BufReader::new(child.stderr.take().unwrap());
|
||||
let until = Instant::now() + Duration::from_secs(5);
|
||||
let mut all = String::new();
|
||||
while Instant::now() < until && !all.contains(want) {
|
||||
let mut line = String::new();
|
||||
if reader.read_line(&mut line).unwrap_or(0) == 0 {
|
||||
break;
|
||||
}
|
||||
all.push_str(&line);
|
||||
}
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
all
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage() {
|
||||
for args in [
|
||||
&[][..],
|
||||
&["serve"][..],
|
||||
&["serve", "--config"][..],
|
||||
&["run", "--config", "x"][..],
|
||||
] {
|
||||
let out = gatewayd().args(args).output().unwrap();
|
||||
assert_eq!(out.status.code(), Some(2), "{args:?}");
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&out.stderr),
|
||||
"usage: gatewayd serve --config <path>\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_bad_config_stops_at_start() {
|
||||
let dir = TempDir::new("main-config");
|
||||
let missing = dir.path().join("nope.toml");
|
||||
let bad = dir.write("bad.toml", "[mattermost]\nurl = \"ftp://x\"\n");
|
||||
for path in [missing, bad] {
|
||||
let out = gatewayd()
|
||||
.arg("serve")
|
||||
.arg("--config")
|
||||
.arg(&path)
|
||||
.output()
|
||||
.unwrap();
|
||||
let err = String::from_utf8_lossy(&out.stderr);
|
||||
assert_eq!(out.status.code(), Some(1), "{err}");
|
||||
assert!(
|
||||
err.starts_with(&format!("gatewayd: {}: ", path.display())),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.ends_with("\nsee docs/runbook.md#gatewayd-start-failed\n"),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_secret_stops_at_start() {
|
||||
let dir = TempDir::new("main-secret");
|
||||
let config = write_config(&dir, "env = \"GW_TEST_TOKEN\"");
|
||||
let out = gatewayd()
|
||||
.arg("serve")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.output()
|
||||
.unwrap();
|
||||
let err = String::from_utf8_lossy(&out.stderr);
|
||||
assert_eq!(out.status.code(), Some(1), "{err}");
|
||||
assert!(
|
||||
err.starts_with("gatewayd: secret mattermost_token: "),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
err.ends_with("\nsee docs/runbook.md#secret-unavailable\n"),
|
||||
"{err}"
|
||||
);
|
||||
assert!(
|
||||
!dir.path().join("home").exists(),
|
||||
"nothing is made before the secret is read"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_secret_in_a_file_warns_and_the_token_is_never_printed() {
|
||||
let dir = TempDir::new("main-file");
|
||||
let secret = dir.write("token", &format!("{TOKEN}\n"));
|
||||
std::fs::set_permissions(&secret, std::fs::Permissions::from_mode(0o600)).unwrap();
|
||||
let config = write_config(&dir, &format!("file = \"{}\"", secret.display()));
|
||||
let mut cmd = gatewayd();
|
||||
cmd.arg("serve").arg("--config").arg(&config);
|
||||
let err = stderr_until(cmd, "trying again");
|
||||
let warning = format!(
|
||||
"gatewayd: warning: secret mattermost_token is read in plaintext from {}; a systemd credential keeps it encrypted at rest (see docs/runbook.md#secret-in-a-file)\n",
|
||||
secret.display()
|
||||
);
|
||||
assert!(err.starts_with(&warning), "{err}");
|
||||
assert!(
|
||||
err.contains("gatewayd: cannot reach http://127.0.0.1:"),
|
||||
"{err}"
|
||||
);
|
||||
assert!(!err.contains(TOKEN), "{err}");
|
||||
let mode = std::fs::metadata(dir.path().join("home/gateway"))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(mode, 0o700);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_secret_from_the_environment_has_no_warning() {
|
||||
let dir = TempDir::new("main-env");
|
||||
let config = write_config(&dir, "env = \"GW_TEST_TOKEN\"");
|
||||
let mut cmd = gatewayd();
|
||||
cmd.arg("serve")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.env("GW_TEST_TOKEN", TOKEN);
|
||||
let err = stderr_until(cmd, "trying again");
|
||||
assert!(err.starts_with("gatewayd: cannot reach "), "{err}");
|
||||
assert!(!err.contains("warning") && !err.contains(TOKEN), "{err}");
|
||||
}
|
||||
Reference in New Issue
Block a user