upgate-presentation 0.1.1

Keep globally installed developer tools up to date
Documentation
use std::time::Duration;

use upgate_domain::{
    AuditFinding, AuditLookupResult, BlockReason, CandidateEvaluationFact, DelayReason,
    ExecutionSupport, ExecutionTargetKind, InstalledTool, ManagerId, PackageName, PlanDiagnostics,
    PlanItem, PlanItemId, ReleaseLookupResult, ToolId, ToolName, UpdateCandidate, UpdatePlan,
    UpdateSeed, UpdateSelectionPolicy, VersionScheme, VersionText,
};
use upgate_presentation::{TargetOption, selection_view};

fn version(value: &str) -> VersionText {
    VersionText::new(value).expect("valid version")
}

fn manager_id() -> ManagerId {
    ManagerId::new("npm").expect("valid manager id")
}

fn installed_tool(package: &str) -> InstalledTool {
    InstalledTool::new(
        manager_id(),
        ToolId::new(package).expect("valid tool id"),
        PackageName::new(package).expect("valid package name"),
        ToolName::new(package).expect("valid tool name"),
        version("1.0.0"),
    )
}

fn finding(id: &str) -> AuditFinding {
    AuditFinding {
        id: id.to_owned(),
        aliases: Vec::new(),
        summary: None,
        severity: None,
        references: Vec::new(),
    }
}

#[test]
fn audit_blocked_picker_option_uses_the_blocked_candidate_version() {
    let audit = AuditLookupResult::Vulnerable {
        findings: vec![finding("GHSA-alpha")],
    };
    let audit_blocking_candidate = CandidateEvaluationFact {
        version: version("2.0.0"),
        age: Some(Duration::from_secs(500)),
        age_allowed: true,
        policy_block_reason: None,
        policy_warning: None,
        audit: Some(audit.clone()),
    };
    let seed = UpdateSeed::new(
        installed_tool("alpha"),
        version("3.0.0"),
        VersionScheme::SemVer,
        ReleaseLookupResult::MissingMetadata,
        ExecutionSupport::exact_only(),
    );
    let plan = UpdatePlan::new(
        manager_id(),
        vec![PlanItem::Blocked {
            id: PlanItemId::new("npm:alpha").expect("valid plan item id"),
            seed,
            reason: BlockReason::AuditVulnerable,
            policy_warnings: Vec::new(),
            diagnostics: PlanDiagnostics {
                candidates: vec![audit_blocking_candidate.clone()],
                audit_blocking_target: Some(audit),
                audit_blocking_candidate: Some(audit_blocking_candidate),
                ..PlanDiagnostics::new(Duration::ZERO)
            },
        }],
    )
    .expect("valid plan");

    let view = selection_view(&plan, &UpdateSelectionPolicy::include_all());
    let option = view.rows[0]
        .target_options
        .first()
        .expect("audit-blocked target option");

    assert!(matches!(
        option,
        TargetOption::ForcedCandidate { target_version, .. } if target_version.as_str() == "2.0.0"
    ));
    assert!(option.has_violation());
}

#[test]
fn delayed_native_brew_row_exposes_a_forced_target_option() {
    let candidate = UpdateCandidate::new(
        ToolId::new("wget").expect("valid tool id"),
        PackageName::new("wget").expect("valid package name"),
        version("1.0.0"),
        version("1.2.0"),
        VersionScheme::ManagerNative,
        ExecutionSupport::native_with_age_bypass_only(),
    )
    .with_execution_target_kind(ExecutionTargetKind::BrewFormula)
    .with_diagnostics(PlanDiagnostics {
        required_age: Duration::from_secs(7 * 24 * 60 * 60),
        ..PlanDiagnostics::new(Duration::from_secs(7 * 24 * 60 * 60))
    });
    let plan = UpdatePlan::new(
        ManagerId::new("brew").expect("valid manager id"),
        vec![PlanItem::Delayed {
            id: PlanItemId::new("brew:wget").expect("valid plan item id"),
            candidate,
            reason: DelayReason::ReleaseTooFresh,
        }],
    )
    .expect("valid plan");

    let view = selection_view(&plan, &UpdateSelectionPolicy::include_all());
    let option = view.rows[0]
        .target_options
        .first()
        .expect("too-fresh brew row should expose a forced target option");

    assert!(matches!(
        option,
        TargetOption::ForcedCandidate { target_version, .. } if target_version.as_str() == "1.2.0"
    ));
}