Files
rustguac/tests/cli_add_admin.rs
T
Dave Kempe bdcf58dd1c docker: let rustguac decide first run, so db_path can move (#240)
The entrypoint created the docker-admin key whenever no file existed at
/opt/rustguac/data/rustguac.db. With db_path set anywhere else, for
example on a Kubernetes persistent volume, that file never exists, so
every container start tried to create the admin again and failed with
"UNIQUE constraint failed: admins.name".

add-admin gains --if-none: create the admin only when no admin account
exists, otherwise say so and exit successfully. rustguac opens the
database db_path names, so the entrypoint no longer needs to know where
it is. Any existing admin counts, so a docker-admin an operator removed
after creating their own does not come back. The save-the-key banner is
printed only when a key was actually created.

Reported by @hit-rich.
2026-10-06 10:41:39 +11:00

99 lines
3.3 KiB
Rust

//! `rustguac add-admin --if-none`, as the Docker entrypoint uses it (#240).
//!
//! The entrypoint used to decide "first run" by looking for a database file
//! at a fixed path, so a config with db_path elsewhere tried to create the
//! admin on every start and failed on the one already there. The check now
//! belongs to rustguac, which reads db_path from the config.
use assert_cmd::Command;
use rusqlite::Connection;
struct ScratchDir(std::path::PathBuf);
impl ScratchDir {
fn new(name: &str) -> Self {
let path =
std::env::temp_dir().join(format!("rustguac-cli-{}-{}", name, std::process::id()));
let _ = std::fs::remove_dir_all(&path);
std::fs::create_dir_all(path.join("elsewhere")).expect("create scratch dir");
// Deliberately not ./data/rustguac.db: the path the old entrypoint
// hardcoded.
std::fs::write(
path.join("config.toml"),
"db_path = \"./elsewhere/custom.db\"\n",
)
.expect("write config");
ScratchDir(path)
}
fn run(&self, args: &[&str]) -> assert_cmd::assert::Assert {
Command::cargo_bin("rustguac")
.unwrap()
.current_dir(&self.0)
.args(["--config", "config.toml"])
.args(args)
.assert()
}
fn admin_count(&self) -> i64 {
Connection::open(self.0.join("elsewhere/custom.db"))
.expect("open database")
.query_row("SELECT COUNT(*) FROM admins", [], |r| r.get(0))
.expect("count admins")
}
}
impl Drop for ScratchDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
#[test]
fn if_none_creates_once_then_skips() {
let dir = ScratchDir::new("add-admin-if-none");
let first = dir
.run(&["add-admin", "--name", "docker-admin", "--if-none"])
.success();
let out = String::from_utf8_lossy(&first.get_output().stdout).to_string();
assert!(
out.contains("API Key:"),
"first run should print a key: {out}"
);
assert_eq!(dir.admin_count(), 1);
// Every later container start: must succeed, print no key, add nothing.
for _ in 0..2 {
let again = dir
.run(&["add-admin", "--name", "docker-admin", "--if-none"])
.success();
let out = String::from_utf8_lossy(&again.get_output().stdout).to_string();
assert!(
!out.contains("API Key:"),
"must not mint another key: {out}"
);
assert!(out.contains("already exist"), "should say why: {out}");
}
assert_eq!(dir.admin_count(), 1);
}
/// An admin under another name still counts: the operator may have removed
/// docker-admin after creating their own, and it must not come back.
#[test]
fn if_none_respects_any_existing_admin() {
let dir = ScratchDir::new("add-admin-if-none-other");
dir.run(&["add-admin", "--name", "ops"]).success();
dir.run(&["add-admin", "--name", "docker-admin", "--if-none"])
.success();
assert_eq!(dir.admin_count(), 1);
}
/// Without the flag, a duplicate is still an error, as before.
#[test]
fn without_if_none_duplicate_still_fails() {
let dir = ScratchDir::new("add-admin-dup");
dir.run(&["add-admin", "--name", "docker-admin"]).success();
dir.run(&["add-admin", "--name", "docker-admin"]).failure();
}