use std::{
collections::VecDeque,
path::PathBuf,
time::{Duration, Instant},
};
use unicode_width::UnicodeWidthStr;
use crossterm::event::{
KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
};
use ratatui::layout::{Position, Rect};
use super::{
ReviewPackage, ReviewSession,
app::{App, Decision},
diagnostic::Diagnostic,
discovery::ReviewQueue,
input::{self, Action},
sources::{Baseline, BaselineLoader},
};
use crate::config::{ApprovalTarget, ApprovedVersion, edit::ConfigFile};
const VIEW_CACHE_CAPACITY: usize = 8;
const RECHECK_INTERVAL: Duration = Duration::from_secs(1);
pub struct QueueState {
pub entries: Vec<Entry>,
pub selected: usize,
pub app: Option<App>,
pub error_diagnostic: Diagnostic,
pub show_failure_help: bool,
pub failure_help: Diagnostic,
pub approval_target: ApprovalTarget,
global_target: Option<ApprovalTarget>,
pub workspace_root: PathBuf,
pub policy_notes: Vec<String>,
loader: Option<BaselineLoader>,
retired_loaders: Vec<BaselineLoader>,
cached_apps: VecDeque<(usize, App)>,
pub sidebar_width: u16,
pub package_rows: Rect,
pub package_start: usize,
package_pointer: Option<Position>,
last_refresh: Instant,
}
impl QueueState {
pub fn new(queue: ReviewQueue) -> Self {
let approval_target = queue.config.target().clone();
let entries: Vec<_> = queue
.packages
.into_iter()
.map(|mut package| {
let approvals = queue.config.config().approved.get(&package.name).cloned();
package.approval = approvals
.as_ref()
.and_then(|approval| approval.for_review(&package.version));
Entry {
package,
approvals,
decision: None,
skipped: false,
error: None,
baseline_unavailable: false,
global_approvals: None,
}
})
.collect();
let selected = entries.iter().position(Entry::actionable).unwrap_or(0);
let sidebar_width = entries
.iter()
.map(|entry| format!("{} v{}", entry.package.name, entry.package.version).width() + 5)
.max()
.unwrap_or(24)
.clamp(24, 36) as u16;
let mut state = Self {
entries,
selected,
error_diagnostic: Diagnostic::default(),
show_failure_help: false,
failure_help: Diagnostic::default(),
app: None,
approval_target,
global_target: queue.global_target,
workspace_root: queue.workspace_root,
policy_notes: queue.policy_notes,
loader: None,
retired_loaders: Vec::new(),
cached_apps: VecDeque::new(),
sidebar_width,
package_rows: Rect::default(),
package_start: 0,
package_pointer: None,
last_refresh: Instant::now(),
};
if !state.entries.is_empty() {
state.open(selected);
}
state
}
pub fn poll(&mut self) -> bool {
self.retired_loaders.retain(|loader| !loader.is_finished());
let mut dirty = false;
if self.last_refresh.elapsed() >= RECHECK_INTERVAL {
self.last_refresh = Instant::now();
self.refresh_global_approvals();
if let Ok(config) = ConfigFile::open(self.approval_target.clone())
&& self.sync_approvals(&config)
{
self.open(self.selected);
}
self.update_current();
dirty = true;
}
if let Some(baseline) = self.loader.as_ref().and_then(BaselineLoader::poll) {
self.entries[self.selected].baseline_unavailable =
matches!(baseline, Baseline::Unavailable { .. });
if let Some(app) = &mut self.app
&& let Err(error) = app.set_baseline(baseline)
{
self.entries[self.selected].error =
Some(format!("could not display baseline: {error:#}"));
self.app = None;
}
self.retire_loader();
dirty = true;
}
dirty
}
fn refresh_global_approvals(&mut self) {
let config = self
.global_target
.as_ref()
.and_then(|target| ConfigFile::open(target.clone()).ok());
for entry in &mut self.entries {
entry.global_approvals = config
.as_ref()
.and_then(|config| config.config().approved.get(&entry.package.name))
.cloned();
}
}
fn sync_approvals(&mut self, config: &ConfigFile) -> bool {
let mut selected_changed = false;
for (index, entry) in self.entries.iter_mut().enumerate() {
let approvals = config.config().approved.get(&entry.package.name);
if entry.approvals.as_ref() == approvals {
continue;
}
entry.baseline_unavailable = false;
entry.approvals = approvals.cloned();
entry.package.approval =
approvals.and_then(|approval| approval.for_review(&entry.package.version));
if approvals.is_some_and(ApprovedVersion::is_blanket)
|| (entry.decision == Some(Decision::Approve)
&& !approvals
.is_some_and(|approval| approval.allows_version(&entry.package.version)))
{
entry.decision = None;
}
selected_changed |= index == self.selected;
}
selected_changed
}
fn update_current(&mut self) {
let Some(app) = &mut self.app else {
return;
};
let entry = &mut self.entries[self.selected];
if let Err(error) = app.refresh_sources() {
entry.error = Some(format!("could not refresh sources: {error:#}"));
self.app = None;
return;
}
Self::present(entry, app);
if entry.already_approved() {
self.retire_loader();
}
self.start_baseline();
}
fn present(entry: &Entry, app: &mut App) {
app.globally_approved = entry.globally_approved();
app.decision = entry.decision;
}
fn start_baseline(&mut self) {
if self.loader.is_some() {
return;
}
if let Some(app) = &self.app
&& !matches!(
app.baseline,
Baseline::Ready(_) | Baseline::Blanket | Baseline::CurrentVersion
)
{
match BaselineLoader::start(&app.session) {
Ok(loader) => self.loader = loader,
Err(error) => self.entries[self.selected].error = Some(format!("{error:#}")),
}
}
}
pub fn navigate(&mut self, backwards: bool) {
let len = self.entries.len();
let selected = if backwards {
(self.selected + len - 1) % len
} else {
(self.selected + 1) % len
};
self.open(selected);
}
pub fn skip(&mut self) -> bool {
self.entries[self.selected].skipped = true;
self.advance()
}
pub fn decide(&mut self, decision: Decision) -> eyre::Result<bool> {
let app = self
.app
.as_mut()
.ok_or_else(|| eyre::eyre!("current package sources could not be loaded"))?;
eyre::ensure!(app.can_decide(), "this package is read-only");
app.decide(decision)?;
let decision = app.decision;
let config = ConfigFile::open(self.approval_target.clone())?;
self.sync_approvals(&config);
let entry = &mut self.entries[self.selected];
entry.decision = decision;
entry.skipped = false;
self.retire_loader();
Ok(self.advance())
}
pub fn handle_key(&mut self, key: KeyEvent) -> Action {
if let Some(app) = &mut self.app {
return input::handle_key(app, key);
}
if key.kind == KeyEventKind::Release {
return Action::Continue;
}
if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
return Action::Quit;
}
if key.code == KeyCode::Char('?') && key.kind == KeyEventKind::Press {
self.show_failure_help = !self.show_failure_help;
self.failure_help.reset_view();
self.error_diagnostic.selection.clear();
return Action::Continue;
}
if self.show_failure_help {
return input::handle_help_key(
&mut self.show_failure_help,
&mut self.failure_help,
key,
);
}
if key.code == KeyCode::Char('y') && self.error_diagnostic.selection.is_active() {
return Action::CopySelection;
}
match key.code {
KeyCode::Esc if self.error_diagnostic.selection.clear() => Action::Continue,
KeyCode::Char('q') | KeyCode::Esc => Action::Quit,
KeyCode::Char('j') => Action::NextPackage,
KeyCode::Char('k') => Action::PreviousPackage,
KeyCode::Char('s') => Action::Skip,
_ => Action::Continue,
}
}
pub fn handle_mouse(&mut self, event: MouseEvent) -> (bool, Action) {
let previous_hover = self.hovered_package();
self.package_pointer = (!event.modifiers.contains(KeyModifiers::SHIFT)
&& !matches!(event.kind, MouseEventKind::Drag(_)))
.then_some(Position::new(event.column, event.row));
let hover_changed = previous_hover != self.hovered_package();
if event.modifiers.contains(KeyModifiers::SHIFT)
|| self.app.as_ref().is_some_and(|app| app.show_help)
|| (self.app.is_none() && self.show_failure_help)
{
return (hover_changed, Action::Continue);
}
if event.kind == MouseEventKind::Down(MouseButton::Left)
&& let Some(selected) = self.hovered_package()
&& selected != self.selected
{
self.open(selected);
return (true, Action::Continue);
}
let source_changed = if let Some(app) = &mut self.app {
app.handle_mouse(event)
} else {
self.error_diagnostic.handle_mouse(event).unwrap_or(false)
};
(hover_changed || source_changed, Action::Continue)
}
pub fn hovered_package(&self) -> Option<usize> {
if (self.app.is_none() && self.show_failure_help)
|| self.app.as_ref().is_some_and(|app| app.show_help)
{
return None;
}
let pointer = self.package_pointer?;
if !self.package_rows.contains(pointer) {
return None;
}
let index = self.package_start + usize::from(pointer.y - self.package_rows.y);
(index < self.entries.len()).then_some(index)
}
pub fn selected_text(&self) -> Option<String> {
if let Some(app) = &self.app {
app.diagnostic
.selected_text()
.or_else(|| app.selection.text(&app.file().rows))
} else {
self.error_diagnostic.selected_text()
}
}
pub fn summary(&self) -> Summary {
let mut summary = Summary::default();
for entry in &self.entries {
match entry.decision {
Some(Decision::Approve) => summary.approved += 1,
Some(Decision::Reject) => summary.rejected += 1,
None if entry.error.is_some() => summary.unresolved += 1,
None if entry.already_approved() => summary.already_approved += 1,
None if entry.baseline_unavailable => summary.unresolved += 1,
None => summary.skipped += 1,
}
}
summary
}
fn open(&mut self, selected: usize) {
self.error_diagnostic.reset_view();
self.retire_loader();
if let Some(app) = self.app.take() {
self.cached_apps.push_back((self.selected, app));
if self.cached_apps.len() > VIEW_CACHE_CAPACITY {
self.cached_apps.pop_front();
}
}
self.selected = selected;
self.entries[selected].error = None;
self.entries[selected].baseline_unavailable = false;
match self.load_current() {
Ok(app) => {
self.app = Some(app);
self.start_baseline();
}
Err(error) => self.entries[selected].error = Some(format!("{error:#}")),
}
}
fn load_current(&mut self) -> eyre::Result<App> {
let config = ConfigFile::open(self.approval_target.clone())?;
self.sync_approvals(&config);
self.refresh_global_approvals();
let entry = &self.entries[self.selected];
let session = ReviewSession {
package: entry.package.clone(),
config,
workspace_root: self.workspace_root.clone(),
policy_notes: self.policy_notes.clone(),
};
let mut app = if let Some(index) = self
.cached_apps
.iter()
.position(|(index, _)| *index == self.selected)
{
let (_, mut app) = self.cached_apps.remove(index).expect("cached index exists");
app.refresh(session)?;
app
} else {
App::new(session)?
};
app.queue_mode = true;
Self::present(&self.entries[self.selected], &mut app);
Ok(app)
}
fn retire_loader(&mut self) {
if let Some(loader) = self.loader.take() {
loader.cancel();
self.retired_loaders.push(loader);
}
self.retired_loaders.retain(|loader| !loader.is_finished());
}
fn advance(&mut self) -> bool {
let len = self.entries.len();
for offset in 1..=len {
let next = (self.selected + offset) % len;
if self.entries[next].actionable() {
self.open(next);
return false;
}
}
true
}
}
pub struct Entry {
pub package: ReviewPackage,
pub decision: Option<Decision>,
pub skipped: bool,
pub error: Option<String>,
pub baseline_unavailable: bool,
approvals: Option<ApprovedVersion>,
global_approvals: Option<ApprovedVersion>,
}
impl Entry {
pub fn status(&self) -> &str {
if self.error.is_some() {
"load error"
} else if self.decision == Some(Decision::Approve) {
"approved"
} else if self.decision == Some(Decision::Reject) {
"rejected"
} else if self.already_approved() {
"already approved"
} else if self.globally_approved() {
"approved globally"
} else if self.skipped {
"skipped"
} else if self.baseline_unavailable {
"baseline unavailable"
} else {
"pending"
}
}
pub fn globally_approved(&self) -> bool {
self.global_approvals
.as_ref()
.is_some_and(|approval| approval.allows_version(&self.package.version))
}
fn already_approved(&self) -> bool {
self.approvals
.as_ref()
.is_some_and(|approval| approval.allows_version(&self.package.version))
}
fn actionable(&self) -> bool {
self.decision.is_none() && !self.skipped && !self.already_approved() && self.error.is_none()
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Summary {
pub approved: usize,
pub rejected: usize,
pub skipped: usize,
pub unresolved: usize,
pub already_approved: usize,
}
impl Summary {
pub fn print(&self) {
println!(
"Review summary: {} approved, {} rejected, {} skipped, {} unresolved, {} already approved.",
self.approved, self.rejected, self.skipped, self.unresolved, self.already_approved
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
fn fixture() -> (tempfile::TempDir, QueueState) {
let (root, metadata) = super::super::tests::fixture();
let queue =
ReviewQueue::from_metadata(&metadata, &crate::args::ReviewOptions::default(), None)
.unwrap();
(root, QueueState::new(queue))
}
fn reopen(state: &QueueState) -> QueueState {
QueueState::new(ReviewQueue {
packages: state
.entries
.iter()
.map(|entry| entry.package.clone())
.collect(),
config: ConfigFile::open(state.approval_target.clone()).unwrap(),
global_target: state.global_target.clone(),
workspace_root: state.workspace_root.clone(),
policy_notes: state.policy_notes.clone(),
})
}
fn with_global_approvals(state: &mut QueueState, versions: &[semver::Version]) -> PathBuf {
let target = ApprovalTarget {
path: state.workspace_root.join("global-approvals.toml"),
scope: crate::config::ApprovalScope::Global,
};
let mut config = ConfigFile::open(target.clone()).unwrap();
for version in versions {
config
.add_version(&state.entries[0].package.name, version)
.unwrap();
}
let path = target.path.clone();
state.global_target = Some(target);
path
}
#[test]
fn blanket_approvals_cover_every_queued_version() {
let (root, state) = fixture();
let policy = "# Manual approval\n[approved]\nduplicate = 'approved'\n";
fs::write(&state.approval_target.path, policy).unwrap();
let mut state = reopen(&state);
let indices: Vec<_> = state
.entries
.iter()
.enumerate()
.filter_map(|(index, entry)| (entry.package.name == "duplicate").then_some(index))
.collect();
assert_eq!(indices.len(), 2);
assert_eq!(state.summary().already_approved, 2);
for index in indices {
state.open(index);
let entry = &state.entries[index];
assert!(entry.already_approved());
assert!(!entry.actionable());
assert_eq!(entry.status(), "already approved");
assert!(state.loader.is_none());
let app = state.app.as_ref().unwrap();
assert!(matches!(app.baseline, Baseline::Blanket));
assert!(!app.can_decide());
for decision in [Decision::Approve, Decision::Reject] {
assert!(state.decide(decision).is_err());
}
assert_eq!(
fs::read_to_string(&state.approval_target.path).unwrap(),
policy
);
}
assert!(!root.path().join("target").exists());
}
#[test]
fn manual_blanket_refresh_clears_decisions_and_removal_restores_review() {
let (_root, mut state) = fixture();
let name = state.entries[0].package.name.clone();
state.decide(Decision::Reject).unwrap();
state.open(0);
assert_eq!(state.entries[0].decision, Some(Decision::Reject));
let policy = format!("[approved]\n{name} = 'approved'\n");
fs::write(&state.approval_target.path, &policy).unwrap();
state.last_refresh -= RECHECK_INTERVAL;
state.poll();
assert_eq!(state.entries[0].decision, None);
assert_eq!(state.entries[0].status(), "already approved");
assert!(matches!(
state.app.as_ref().unwrap().baseline,
Baseline::Blanket
));
assert!(!state.app.as_ref().unwrap().can_decide());
assert!(state.loader.is_none());
assert_eq!(
fs::read_to_string(&state.approval_target.path).unwrap(),
policy
);
fs::write(&state.approval_target.path, "[approved]\n").unwrap();
state.last_refresh -= RECHECK_INTERVAL;
state.poll();
assert!(state.entries[0].actionable());
assert!(state.app.as_ref().unwrap().can_decide());
assert!(matches!(
state.app.as_ref().unwrap().baseline,
Baseline::NewPackage
));
}
#[test]
fn global_blanket_is_informational_until_selected_as_the_write_target() {
let (_root, mut state) = fixture();
let name = &state.entries[0].package.name;
let target = ApprovalTarget {
path: state.workspace_root.join("global-approvals.toml"),
scope: crate::config::ApprovalScope::Global,
};
let policy = format!("[approved]\n{name} = 'approved'\n");
fs::write(&target.path, &policy).unwrap();
state.global_target = Some(target.clone());
let mut state = reopen(&state);
assert!(state.entries[0].globally_approved());
assert!(state.entries[0].actionable());
assert_eq!(state.entries[0].status(), "approved globally");
assert_eq!(
state.app.as_ref().unwrap().displayed_approval_scope(),
Some(crate::config::ApprovalScope::Global)
);
assert!(state.app.as_ref().unwrap().can_decide());
state.decide(Decision::Approve).unwrap();
assert_eq!(fs::read_to_string(&target.path).unwrap(), policy);
let workspace_policy = fs::read(&state.approval_target.path).unwrap();
let workspace_path = state.approval_target.path.clone();
state.approval_target = target.clone();
state.global_target = None;
let mut state = reopen(&state);
state.open(0);
assert!(state.entries[0].already_approved());
assert!(matches!(
state.app.as_ref().unwrap().baseline,
Baseline::Blanket
));
assert_eq!(
state.app.as_ref().unwrap().displayed_approval_scope(),
Some(crate::config::ApprovalScope::Global)
);
for decision in [Decision::Approve, Decision::Reject] {
assert!(state.decide(decision).is_err());
}
assert_eq!(fs::read_to_string(&target.path).unwrap(), policy);
assert_eq!(fs::read(&workspace_path).unwrap(), workspace_policy);
}
#[test]
fn global_version_is_informational_and_local_saves_remain_isolated() {
let (root, mut state) = fixture();
let version = state.entries[0].package.version.clone();
let global = with_global_approvals(&mut state, std::slice::from_ref(&version));
let global_contents = fs::read_to_string(&global).unwrap();
let mut state = reopen(&state);
assert!(state.entries[0].globally_approved());
assert!(state.entries[0].actionable());
assert!(!state.entries[0].already_approved());
assert!(state.app.as_ref().unwrap().globally_approved);
assert!(state.app.as_ref().unwrap().can_decide());
assert_eq!(state.app.as_ref().unwrap().session.package.approval, None);
assert_eq!(state.summary().already_approved, 0);
assert!(!root.path().join("approved-builds.toml").exists());
state.decide(Decision::Approve).unwrap();
state.open(0);
assert!(state.entries[0].globally_approved());
assert!(state.entries[0].already_approved());
assert!(!state.app.as_ref().unwrap().can_decide());
assert_eq!(state.entries[0].status(), "approved");
assert_eq!(fs::read_to_string(&global).unwrap(), global_contents);
let local = ConfigFile::open(state.approval_target.clone()).unwrap();
assert!(local.config().approved[&state.entries[0].package.name].allows_version(&version));
let reopened = reopen(&state);
assert_eq!(reopened.entries[0].status(), "already approved");
}
#[test]
fn removed_or_invalid_global_documents_clear_status_without_blocking_local_review() {
let (_root, mut state) = fixture();
let version = state.entries[0].package.version.clone();
let global = with_global_approvals(&mut state, &[version]);
let contents = fs::read_to_string(&global).unwrap();
let mut state = reopen(&state);
assert!(state.entries[0].globally_approved());
fs::remove_file(&global).unwrap();
state.last_refresh -= RECHECK_INTERVAL;
state.poll();
assert!(!state.entries[0].globally_approved());
assert!(!state.app.as_ref().unwrap().globally_approved);
fs::write(&global, &contents).unwrap();
state.open(0);
assert!(state.app.as_ref().unwrap().globally_approved);
fs::write(&global, "not valid [toml").unwrap();
state.last_refresh -= RECHECK_INTERVAL;
state.poll();
assert!(!state.app.as_ref().unwrap().globally_approved);
assert!(state.entries[0].actionable());
state.decide(Decision::Approve).unwrap();
assert!(state.entries[0].already_approved());
assert_eq!(fs::read_to_string(&global).unwrap(), "not valid [toml");
}
#[test]
fn global_navigation_reopens_additively_and_save_conflicts_stay_undecided() {
let (root, metadata) = super::super::tests::fixture();
let external = tempfile::tempdir().unwrap();
let global = external.path().join("approvals.toml");
fs::write(&global, "[approved]\ncustom-script='1.0.0'\n").unwrap();
let options = crate::args::ReviewOptions {
global: true,
..Default::default()
};
let queue = ReviewQueue::from_metadata(&metadata, &options, Some(&global)).unwrap();
let notes = queue.policy_notes.clone();
let mut state = QueueState::new(queue);
let index = state
.entries
.iter()
.position(|entry| entry.package.name == "custom-script")
.unwrap();
state.open(index);
state.navigate(false);
state.navigate(true);
assert_eq!(state.selected, index);
assert_eq!(
state.app.as_ref().unwrap().session.workspace_root,
metadata.workspace_root.as_std_path()
);
assert_eq!(state.app.as_ref().unwrap().session.policy_notes, notes);
assert_eq!(
state.app.as_ref().unwrap().session.config.target().scope,
crate::config::ApprovalScope::Global
);
state.decide(Decision::Approve).unwrap();
assert_eq!(state.entries[index].decision, Some(Decision::Approve));
let saved = fs::read_to_string(&global).unwrap();
let config = ConfigFile::open(state.approval_target.clone()).unwrap();
assert_eq!(
config.config().approved["custom-script"],
ApprovedVersion::Multiple(vec![
semver::Version::new(1, 0, 0),
semver::Version::new(2, 0, 0)
])
);
assert!(!root.path().join("approved-builds.toml").exists());
let mut reopened = reopen(&state);
assert_eq!(
reopened.approval_target.scope,
crate::config::ApprovalScope::Global
);
assert_eq!(
reopened.approval_target.path,
global.canonicalize().unwrap()
);
assert_eq!(reopened.workspace_root, state.workspace_root);
let next = reopened
.entries
.iter()
.position(|entry| entry.package.name == "default-script")
.unwrap();
reopened.open(next);
fs::write(&global, format!("{saved}\n# concurrent edit\n")).unwrap();
assert!(reopened.decide(Decision::Approve).is_err());
assert_eq!(reopened.selected, next);
assert_eq!(reopened.entries[next].decision, None);
assert_eq!(reopened.app.as_ref().unwrap().decision, None);
assert_eq!(
fs::read_to_string(&global).unwrap(),
format!("{saved}\n# concurrent edit\n")
);
}
#[test]
fn navigation_remains_available_without_sources() {
let (_root, mut state) = fixture();
state.app = None;
for (character, action) in [
('j', Action::NextPackage),
('k', Action::PreviousPackage),
('n', Action::Continue),
('p', Action::Continue),
] {
let key = KeyEvent::new(KeyCode::Char(character), KeyModifiers::NONE);
assert_eq!(state.handle_key(key), action);
assert_eq!(
state.handle_key(KeyEvent {
kind: KeyEventKind::Release,
..key
}),
Action::Continue
);
}
}
#[test]
fn cached_views_preserve_rows_and_scroll_but_refresh_sources_and_approvals() {
let (root, mut state) = fixture();
state.app.as_mut().unwrap().set_viewport(1, 10);
state.app.as_mut().unwrap().scroll(1, 0);
let top = state.app.as_ref().unwrap().top;
state.navigate(false);
state.navigate(true);
assert_eq!(state.app.as_ref().unwrap().top, top);
state.navigate(false);
fs::write(
root.path().join("deps/custom/scripts/custom.rs"),
"fn changed() {}\n",
)
.unwrap();
state.navigate(true);
let text = state.app.as_ref().unwrap().file().rows[0].text.to_string();
assert_eq!(text, "fn changed() {}");
assert_eq!(state.app.as_ref().unwrap().top, 0);
state.navigate(false);
fs::write(
root.path().join("approved-builds.toml"),
"[approved]\ncustom-script='2.0.0'\n",
)
.unwrap();
state.navigate(true);
assert!(state.app.as_ref().unwrap().is_current_version_approved());
assert!(matches!(
state.app.as_ref().unwrap().baseline,
Baseline::CurrentVersion
));
}
#[test]
fn cached_baselines_do_not_restart_source_retrieval() {
use crate::review::sources::PackageSources;
let (_root, mut state) = fixture();
let app = state.app.as_mut().unwrap();
let version = "1.0.0".parse::<semver::Version>().unwrap();
app.session
.config
.add_version("custom-script", &version)
.unwrap();
app.session.package.approval = Some(ApprovedVersion::SemVer(version.clone()));
app.set_baseline(Baseline::Ready(PackageSources {
version,
scripts: vec![],
}))
.unwrap();
state.navigate(false);
state.navigate(true);
assert!(matches!(
state.app.as_ref().unwrap().baseline,
Baseline::Ready(_)
));
assert!(state.loader.is_none());
}
#[test]
fn cached_view_keeps_save_conflict_detection() {
let (root, mut state) = fixture();
state.navigate(false);
state.navigate(true);
let external = "[approved]\nother='1.0.0'\n";
fs::write(root.path().join("approved-builds.toml"), external).unwrap();
assert!(state.decide(Decision::Approve).is_err());
assert_eq!(
fs::read_to_string(root.path().join("approved-builds.toml")).unwrap(),
external
);
}
#[test]
fn offscreen_approval_removal_clears_the_saved_decision() {
let (_root, mut state) = fixture();
let index = state.selected;
assert!(!state.decide(Decision::Approve).unwrap());
assert_ne!(state.selected, index);
assert_eq!(state.entries[index].status(), "approved");
fs::write(&state.approval_target.path, "[approved]\n").unwrap();
state.last_refresh -= RECHECK_INTERVAL;
assert!(state.poll());
assert_ne!(state.selected, index);
assert_eq!(state.entries[index].decision, None);
assert!(state.entries[index].actionable());
assert_eq!(state.summary().approved, 0);
}
#[test]
fn view_cache_is_bounded() {
let (_root, metadata) = super::super::tests::fixture();
let mut queue =
ReviewQueue::from_metadata(&metadata, &crate::args::ReviewOptions::default(), None)
.unwrap();
let template = queue.packages[0].clone();
for index in 0..VIEW_CACHE_CAPACITY + 4 {
let mut package = template.clone();
package.name = format!("cache-fixture-{index}");
package.id.repr = format!("path+file:///cache-fixture-{index}#1.0.0");
queue.packages.push(package);
}
let mut state = QueueState::new(queue);
for _ in 0..state.entries.len() * 2 {
state.navigate(false);
assert!(state.cached_apps.len() <= VIEW_CACHE_CAPACITY);
}
}
#[test]
#[ignore = "manual navigation performance measurement"]
fn navigation_benchmark() {
let (root, metadata) = super::super::tests::fixture();
let code: String = (0..200).map(|i| format!("fn helper_{i}() {{ let message = \"build script input\"; println!(\"{{message}}\"); }}\n")).collect();
let queue =
ReviewQueue::from_metadata(&metadata, &crate::args::ReviewOptions::default(), None)
.unwrap();
for package in &queue.packages {
for script in &package.scripts {
fs::write(&script.path, &code).unwrap();
}
}
let cold_start = std::time::Instant::now();
let mut state = QueueState::new(queue);
for _ in 0..state.entries.len() {
state.navigate(false);
}
println!(
"First pass through {} packages: {:?}",
state.entries.len(),
cold_start.elapsed()
);
let start = std::time::Instant::now();
for _ in 0..40 {
state.navigate(false);
}
let elapsed = start.elapsed();
println!(
"40 package switches: {elapsed:?} ({:?}/switch)",
elapsed / 40
);
std::hint::black_box(&state.app);
drop(root);
}
#[test]
fn partial_completion_survives_reopening_the_queue() {
let (root, mut state) = fixture();
state.decide(Decision::Approve).unwrap();
assert_eq!(
state.summary(),
Summary {
approved: 1,
skipped: 3,
..Default::default()
}
);
drop(state);
let metadata = cargo_metadata::MetadataCommand::new()
.manifest_path(root.path().join("Cargo.toml"))
.exec()
.unwrap();
let reopened = QueueState::new(
ReviewQueue::from_metadata(&metadata, &crate::args::ReviewOptions::default(), None)
.unwrap(),
);
assert_eq!(
reopened.entries[reopened.selected].package.name,
"default-script"
);
assert_eq!(
reopened.summary(),
Summary {
already_approved: 1,
skipped: 3,
..Default::default()
}
);
}
#[test]
fn version_approvals_are_read_only_and_other_packages_are_actionable() {
let (root, metadata) = super::super::tests::fixture();
fs::write(
root.path().join("approved-builds.toml"),
"[approved]\ncustom-script='2.0.0'\nduplicate=['1.0.0','2.0.0']\n",
)
.unwrap();
let mut state = QueueState::new(
ReviewQueue::from_metadata(&metadata, &crate::args::ReviewOptions::default(), None)
.unwrap(),
);
assert_eq!(state.selected, 1);
assert!(state.app.as_ref().unwrap().can_decide());
state.navigate(true);
assert!(!state.app.as_ref().unwrap().can_decide());
assert_eq!(
state.summary(),
Summary {
already_approved: 3,
skipped: 1,
..Default::default()
}
);
}
#[test]
fn decisions_advance_and_preserve_previous_saves() {
let (root, mut state) = fixture();
assert_eq!(state.entries[state.selected].package.name, "custom-script");
assert!(!state.decide(Decision::Approve).unwrap());
assert_eq!(state.entries[state.selected].package.name, "default-script");
assert!(!state.decide(Decision::Approve).unwrap());
assert_eq!(state.entries[state.selected].package.name, "duplicate");
assert!(!state.decide(Decision::Approve).unwrap());
assert!(state.decide(Decision::Approve).unwrap());
let saved =
crate::config::load_config_from(&root.path().join("approved-builds.toml")).unwrap();
assert_eq!(saved.approved.len(), 3);
assert!(saved.approved["duplicate"].allows_version(&"1.0.0".parse().unwrap()));
assert!(saved.approved["duplicate"].allows_version(&"2.0.0".parse().unwrap()));
assert_eq!(
state.summary(),
Summary {
approved: 4,
..Default::default()
}
);
}
#[test]
fn reject_skip_and_quit_leave_other_packages_pending() {
let (root, mut state) = fixture();
assert!(!state.decide(Decision::Reject).unwrap());
assert!(!state.skip());
assert!(!root.path().join("approved-builds.toml").exists());
assert_eq!(
state.summary(),
Summary {
rejected: 1,
skipped: 3,
..Default::default()
}
);
}
#[test]
fn approved_packages_are_read_only_but_other_versions_are_actionable() {
let (_root, mut state) = fixture();
state.decide(Decision::Approve).unwrap();
state.navigate(true);
assert!(!state.app.as_ref().unwrap().can_decide());
state.navigate(true);
assert_eq!(state.entries[state.selected].package.name, "duplicate");
assert!(state.app.as_ref().unwrap().can_decide());
assert!(state.decide(Decision::Approve).is_ok());
}
#[test]
fn conflicts_on_save_do_not_advance_or_change_decisions() {
let (root, mut state) = fixture();
let external = "[approved]\nother='1.0.0'\n";
fs::write(root.path().join("approved-builds.toml"), external).unwrap();
let index = state.selected;
assert!(state.decide(Decision::Approve).is_err());
assert_eq!(state.selected, index);
assert_eq!(state.entries[index].decision, None);
assert_eq!(
fs::read_to_string(root.path().join("approved-builds.toml")).unwrap(),
external
);
}
#[test]
fn missing_source_is_unresolved_and_navigation_still_works() {
let (root, mut state) = fixture();
fs::remove_file(root.path().join("deps/default/build.rs")).unwrap();
state.navigate(false);
assert!(state.app.is_none());
assert!(state.entries[state.selected].error.is_some());
assert_eq!(state.summary().unresolved, 1);
state.navigate(true);
assert!(state.app.is_some());
}
}