| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- //! CLI-driver tests (todo 29) against the `fake-d-oauth.sh` fixture.
- //!
- //! The fixture is copied into a per-test tempdir and made executable with
- //! `std` permissions (no shell `chmod`); every driver run gets a fresh
- //! `FAKE_STATE_DIR`. The minted `IATTEST`-prefixed tokens are synthetic test
- //! values, never real credentials.
- use std::{fs, path::PathBuf};
- use provision::cli_driver::{
- DEMO_POST_LOGOUT_URI, DEMO_REDIRECT_URI, DEMO_SCOPE, Driver, SetupInputs, load_env_file,
- run_setup,
- };
- /// Process-env fixture state is global: serialize the CLI-driver tests so two
- /// tests never point `FAKE_STATE_DIR` at different tempdirs at once.
- static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
- /// Install the fixture as an executable in a fresh tempdir; return the
- /// tempdir (kept alive by the caller), the CLI path, and the state dir.
- fn install_fake_cli() -> (tempfile::TempDir, PathBuf, PathBuf) {
- let dir = tempfile::tempdir().expect("tempdir");
- let cli = dir.path().join("fake-d-oauth.sh");
- let source = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fake-d-oauth.sh");
- let script = fs::read_to_string(&source).expect("read fixture");
- fs::write(&cli, script).expect("write fake cli");
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt as _;
- fs::set_permissions(&cli, fs::Permissions::from_mode(0o755)).expect("chmod fake cli");
- }
- let state = dir.path().join("state");
- fs::create_dir(&state).expect("state dir");
- unsafe {
- std::env::set_var("FAKE_STATE_DIR", &state);
- }
- (dir, cli, state)
- }
- /// Minimal `.env` file: comments, blanks, and quoted values included.
- fn write_env_file(dir: &tempfile::TempDir) -> PathBuf {
- let path = dir.path().join("d-oauth.env");
- fs::write(
- &path,
- "# fake operator env\nD_OAUTH_ISSUER=https://localhost:8080\nQUOTED=\"hello world\"\nEMPTY=\n",
- )
- .expect("write env file");
- path
- }
- fn write_password_file(dir: &tempfile::TempDir) -> PathBuf {
- let path = dir.path().join("demo-password.txt");
- fs::write(&path, "correct-horse-battery-staple").expect("write password file");
- path
- }
- fn run_driver(cli: &std::path::Path, env_file: &std::path::Path, pw: &std::path::Path) -> String {
- let outcome = run_setup(&SetupInputs {
- cli: cli.to_owned(),
- env_file: env_file.to_owned(),
- email: "demo@example.com".to_owned(),
- password_file: pw.to_owned(),
- post_logout_uri: DEMO_POST_LOGOUT_URI.to_owned(),
- })
- .expect("setup succeeds");
- outcome.initial_access_token
- }
- #[test]
- fn cli_driver_runs_full_sequence_and_captures_iat() {
- let _guard = SERIAL.lock().expect("serial guard");
- let (dir, cli, state) = install_fake_cli();
- let env_file = write_env_file(&dir);
- let pw = write_password_file(&dir);
- let iat = run_driver(&cli, &env_file, &pw);
- assert_eq!(iat, format!("IATTEST{:037}", 1));
- let log = fs::read_to_string(state.join("call.log")).expect("call log");
- let mut calls = log.lines();
- assert_eq!(
- calls.next(),
- Some(format!("scope create {DEMO_SCOPE}").as_str())
- );
- assert_eq!(calls.next(), Some("user scopes --email demo@example.com"));
- let create = calls.next().expect("user create call");
- assert!(create.starts_with("user create --email demo@example.com --password-file "));
- assert_eq!(
- calls.next(),
- Some(format!("user grant-scope --email demo@example.com --scope {DEMO_SCOPE}").as_str())
- );
- assert_eq!(
- calls.next(),
- Some(format!("redirect-uri create {DEMO_REDIRECT_URI}").as_str())
- );
- assert_eq!(
- calls.next(),
- Some(format!("redirect-uri create {DEMO_POST_LOGOUT_URI} --kind post_logout").as_str())
- );
- assert_eq!(
- calls.next(),
- Some(format!("redirect-uri enable {DEMO_POST_LOGOUT_URI} --kind post_logout").as_str())
- );
- assert_eq!(
- calls.next(),
- Some("initial-access-token issue --uses 1 --ttl 600")
- );
- assert_eq!(calls.next(), None);
- // The IAT travels on stdout into the DCR step only: it must not appear in
- // the argv log or in any persisted fixture state.
- assert!(!log.contains("IATTEST"));
- let _ = dir;
- }
- #[test]
- fn cli_driver_second_run_skips_existing_user() {
- let _guard = SERIAL.lock().expect("serial guard");
- let (dir, cli, state) = install_fake_cli();
- let env_file = write_env_file(&dir);
- let pw = write_password_file(&dir);
- assert_eq!(
- run_driver(&cli, &env_file, &pw),
- format!("IATTEST{:037}", 1)
- );
- // Second run: `user create` hits the already-exists path and must not fail.
- assert_eq!(
- run_driver(&cli, &env_file, &pw),
- format!("IATTEST{:037}", 2)
- );
- let log = fs::read_to_string(state.join("call.log")).expect("call log");
- assert_eq!(
- log
- .lines()
- .filter(|line| line.starts_with("user create "))
- .count(),
- 2
- );
- assert!(!log.contains("IATTEST"));
- let _ = dir;
- }
- #[test]
- fn cli_driver_surfaces_missing_scope_as_typed_error() {
- let _guard = SERIAL.lock().expect("serial guard");
- let (dir, cli, _state) = install_fake_cli();
- let env_file = write_env_file(&dir);
- let pw = write_password_file(&dir);
- unsafe {
- std::env::set_var("FAKE_FAIL_STEP", "grant");
- }
- let result = run_setup(&SetupInputs {
- cli: cli.to_owned(),
- env_file: env_file.to_owned(),
- email: "demo@example.com".to_owned(),
- password_file: pw.to_owned(),
- post_logout_uri: DEMO_POST_LOGOUT_URI.to_owned(),
- });
- unsafe {
- std::env::remove_var("FAKE_FAIL_STEP");
- }
- match result {
- Err(provision::cli_driver::CliDriverError::Step { step, stderr, .. }) => {
- assert_eq!(step, "user grant-scope");
- assert!(
- stderr.contains("not found: scope demo.read"),
- "stderr: {stderr}"
- );
- }
- other => panic!("expected a typed grant-scope failure, got {other:?}"),
- }
- let _ = dir;
- }
- #[test]
- fn cli_driver_parses_env_file_without_touching_process_env() {
- let _guard = SERIAL.lock().expect("serial guard");
- let (dir, _cli, _state) = install_fake_cli();
- let env_file = write_env_file(&dir);
- let env = load_env_file(&env_file).expect("parse env file");
- assert_eq!(
- env.get("D_OAUTH_ISSUER").map(String::as_str),
- Some("https://localhost:8080")
- );
- assert_eq!(env.get("QUOTED").map(String::as_str), Some("hello world"));
- assert_eq!(env.get("EMPTY").map(String::as_str), Some(""));
- // Parsing must not export into our own process environment.
- assert!(std::env::var("D_OAUTH_ISSUER").is_err());
- let _ = dir;
- }
- #[test]
- fn cli_driver_driver_struct_is_usable_directly() {
- let _guard = SERIAL.lock().expect("serial guard");
- let (dir, cli, state) = install_fake_cli();
- let env_file = write_env_file(&dir);
- let pw = write_password_file(&dir);
- let env = load_env_file(&env_file).expect("parse env file");
- let driver = Driver {
- cli: cli.clone(),
- env,
- };
- let outcome = driver
- .run("demo@example.com", &pw, DEMO_POST_LOGOUT_URI)
- .expect("driver run");
- assert_eq!(outcome.initial_access_token, format!("IATTEST{:037}", 1));
- assert!(state.join("scopes.txt").exists());
- let _ = dir;
- }
|