mod auth;
use super::by_hand;
use std::path::Path;
use std::sync::mpsc;
use tempfile::tempdir;
use super::{LoginRun, start};
use crate::cli_outbound::{Chunk, Cli, ExitInfo, Streamed};
use crate::opslog::{self, SYNTHETIC_EXIT};
fn argv() -> Vec<String> {
["bz", "--login", "--provider", "openai", "--browser"]
.into_iter()
.map(String::from)
.collect()
}
fn texts(view: &super::LoginView) -> Vec<String> {
view.lines.iter().map(|l| l.text.clone()).collect()
}
#[test]
fn login_run_streams_lines_live_then_logs_a_clean_outcome_row() {
let dir = tempdir().unwrap();
let (tx, rx) = mpsc::channel();
let mut run = LoginRun::from_streamed(Streamed::from_rx(rx), argv(), dir.path());
assert!(run.poll());
assert!(run.view().lines.is_empty());
tx.send(Chunk::Stderr(
b"To authorize, open https://x/auth\n".to_vec(),
))
.unwrap();
tx.send(Chunk::Stdout(b"noise\n".to_vec())).unwrap();
assert!(run.poll());
assert_eq!(
texts(&run.view()),
vec!["To authorize, open https://x/auth", "noise"]
);
tx.send(Chunk::Exited(ExitInfo::Code(0))).unwrap();
assert!(!run.poll());
let view = run.view();
assert_eq!(view.outcome, Some(0));
assert_eq!(view.fallback, None);
assert!(!run.poll());
let ops = opslog::tail(dir.path(), 8);
assert_eq!(ops.len(), 1);
assert_eq!(ops[0].argv, argv());
assert_eq!(ops[0].exit, 0);
assert_eq!(ops[0].stdout, "");
}
#[test]
fn the_run_by_hand_spelling_binds_the_workspace_or_says_there_is_none() {
assert_eq!(
by_hand(Some(Path::new("/spheres/corp")), "anthropic"),
"yog exec --ws /spheres/corp bz --login --provider anthropic --browser"
);
let none = by_hand(None, "anthropic");
assert!(none.contains("no workspace"), "{none}");
assert!(
!none.starts_with("yog exec"),
"not offered as runnable: {none}"
);
}
#[test]
fn login_run_nonzero_exit_carries_the_fallback_command_and_stderr() {
let dir = tempdir().unwrap();
let (tx, rx) = mpsc::channel();
let mut run = LoginRun::from_streamed(Streamed::from_rx(rx), argv(), dir.path());
tx.send(Chunk::Stderr(
b"provider `local` has no `oauth` config; add an `oauth` block to its row\n".to_vec(),
))
.unwrap();
tx.send(Chunk::Exited(ExitInfo::Code(78))).unwrap();
assert!(!run.poll());
let view = run.view();
assert_eq!(view.outcome, Some(78));
assert_eq!(
texts(&view),
vec!["provider `local` has no `oauth` config; add an `oauth` block to its row"],
"bz's terminal reason/remedy line reaches the pane"
);
assert_eq!(
view.fallback.as_deref(),
Some("yog exec --ws /ws bz --login --provider openai --browser")
);
let ops = opslog::tail(dir.path(), 8);
assert_eq!(ops[0].exit, 78);
assert_eq!(ops[0].stderr, texts(&view).join("\n"));
}
#[test]
fn start_logs_a_synthetic_row_when_bz_cannot_spawn() {
let dir = tempdir().unwrap();
let bz = Cli::new("/definitely/not/a/real/bz-xyz");
let err = start(&bz, "openai", dir.path(), "TS", Some(Path::new("/ws")))
.err()
.unwrap();
assert_eq!(err.kind(), std::io::ErrorKind::Other);
let ops = opslog::tail(dir.path(), 8);
assert_eq!(ops.len(), 1);
assert_eq!(ops[0].exit, SYNTHETIC_EXIT);
assert_eq!(
ops[0].argv,
[
"/definitely/not/a/real/bz-xyz",
"--login",
"--provider",
"openai",
"--browser",
]
);
assert!(!ops[0].stderr.is_empty());
}