Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

xtask/tests/browser_step.rs

//! RA2-7: the A29-20 browser test (`passkeys_at_gunnar::first_touch`) must RUN in CI. Without
//! a Chromium it says NOT RUN and passes, and gunnar-ui's CI had no Chromium step, so the
//! owner's bug could come back with CI green. The plan now carries a `browser` step: the
//! first-touch tests in a browser image, with the browser required.

use nornir_xtask_support::ci::Mode;

#[test]
fn the_quick_plan_runs_the_browser_tests_in_their_image() {
    let plan = xtask::ci::plan(&xtask::workspace_root());
    let step = plan.steps.iter().find(|s| s.label() == "browser").expect("gunnar-ui's CI plan has a `browser` step");
    assert!(step.runs_in(Mode::Quick), "the browser step runs on every push (quick)");
    assert_eq!(step.describe(), "[host] cargo xtask browser", "it enters its image itself, so it is a host step");
}

#[test]
fn the_image_tag_follows_the_recipe_and_is_stable() {
    assert_eq!(xtask::ci::recipe_tag(b""), "cbf29ce484222325", "FNV-1a 64 of nothing");
    assert_eq!(xtask::ci::recipe_tag(b"a"), "af63dc4c8601ec8c");
    assert_ne!(xtask::ci::recipe_tag(b"FROM x"), xtask::ci::recipe_tag(b"FROM y"));
    let file = xtask::workspace_root().join("browser/Containerfile");
    let recipe = std::fs::read_to_string(&file).expect("browser/Containerfile");
    assert!(recipe.contains("chromium") && recipe.contains("ENV CHROME="), "the image carries a Chromium and names it");
}

/// ★ GHUNT-17: the image is the recipe ON its base. Keyed by the Containerfile alone, an image
/// built on ci3 (no ssh) stayed in use after the toolchain base moved to ci4. The tag now
/// follows the base image's id too: the same recipe on another base is another image.
#[test]
fn the_image_tag_follows_the_base_image_too() {
    let recipe = b"ARG BASE\nFROM ${BASE}\n";
    let ci3 = "sha256:a4b5dac5a70386525a0fed6793221b155c2e606b677149eca383961e45f90437";
    let ci4 = "sha256:875a7a325dfe7f6b838535d085a2a502e8ad250076c947f6d023b1e224f03334";
    assert_ne!(xtask::ci::image_tag(recipe, ci3), xtask::ci::image_tag(recipe, ci4), "another base, another image");
    assert_eq!(xtask::ci::image_tag(recipe, ci4), xtask::ci::image_tag(recipe, ci4), "stable for the same pair");
    assert_ne!(xtask::ci::image_tag(recipe, ci4), xtask::ci::image_tag(b"FROM y", ci4), "another recipe, another image");
    assert_ne!(xtask::ci::image_tag(recipe, ci4), xtask::ci::recipe_tag(recipe), "never the recipe-only key");
    assert_eq!(xtask::ci::image_tag(recipe, ci4).len(), 16, "16 hex digits, as before");
    let main = std::fs::read_to_string(xtask::workspace_root().join("xtask/src/main.rs")).expect("xtask/src/main.rs");
    assert!(
        main.contains("xtask::ci::image_tag(") && !main.contains("xtask::ci::recipe_tag("),
        "`cargo xtask browser` tags its image by the recipe AND the base"
    );
}

/// ★ The paid sign-up page back from paying (`signup_paid_return_browser.rs`) is a browser test
/// too: without a Chromium it said NOT RUN and passed, and the `browser` step ran only the first
/// touch, so "Stripe ONCE per buy" was never checked in CI. The step runs it, browser required.
#[test]
fn the_browser_step_runs_the_paid_signup_browser_test_too() {
    let runs = xtask::ci::browser_runs(None);
    let lines: Vec<String> = runs.iter().map(|r| r.join(" ")).collect();
    assert!(
        lines.iter().any(|l| l.contains("--lib passkeys_at_gunnar::first_touch") && l.contains("--features metered")),
        "the first touch: {lines:#?}"
    );
    assert!(
        lines.iter().any(|l| l.contains("--test signup_paid_return_browser") && l.contains("--features metered")),
        "the paid sign-up browser test: {lines:#?}"
    );
    assert!(
        lines.iter().any(|l| l.contains("--lib anonymous::public_site::served::in_the_browser") && l.contains("--features metered,public-site")),
        "GUN-LANDING, the front page in the browser: {lines:#?}"
    );
    assert!(
        lines.iter().any(|l| l.contains("--lib anonymous::console_address::prerendered::in_the_browser") && l.contains("--features metered,public-site")),
        "GUN-GUEST, a deep link in the console, guest and signed in: {lines:#?}"
    );
    assert_eq!(xtask::ci::browser_runs(Some("x::y")).len(), 1, "a filter runs that one");
}

/// ★ GUN-GUEST: the deep-link test drives the console's REAL bundle; the step hands it the
/// bundle it names (`GUNNAR_UI_BUNDLE`, or the recipe's `dist-metered` when one is built).
#[test]
fn the_browser_step_hands_the_built_bundle_to_its_tests() {
    let main = std::fs::read_to_string(xtask::workspace_root().join("xtask/src/main.rs")).expect("xtask/src/main.rs");
    assert!(main.contains("GUNNAR_UI_BUNDLE"), "`cargo xtask browser` passes the bundle on");
}

/// ★ **The image runs the console matrix too** (GUN-FIELD-TEST, 2026-11-01): `gunnar-uitest`
/// (uitest/src/rig.rs) spawns `ssh-keygen`, `ssh` and `git`, and in
/// `localhost/gunnar-ui-browser:c3ecdcf7f2869b7e` the rig stopped at once:
/// "rig: ssh-keygen: No such file or directory". The matrix ran only in an older hand-built
/// image. The recipe installs the OpenSSH client beside Chromium (git is the base image's).
#[test]
fn the_image_carries_what_the_console_matrix_spawns() {
    let file = xtask::workspace_root().join("browser/Containerfile");
    let recipe = std::fs::read_to_string(&file).expect("browser/Containerfile");
    assert!(recipe.contains("openssh-client"), "ssh and ssh-keygen for the uitest rig: {recipe}");
}