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

nordisk/holger-ui

browser-tests/tests/set_votes.rs

//! ★ **A set vote names the revision the console showed** (SEC-REVIEW 2026-11-01 D4, holger
//! main 2f8d0237). holger binds a dependency-set vote to the set the approver was shown: the
//! vote names the set's `revision`, and a vote naming none, or another than the set has now
//! (a re-pull changed its members), is refused with 409 and holger's `holger-depset-4` words,
//! and nothing is cast. In a real browser, on the Security page: a vote on a set that changed
//! after the page drew it shows holger's refusal and casts nothing; after the page reloads the
//! set, the vote names the revision it shows and is taken.

use holger_ui_browser_tests::browser::{Browser, Tab};
use holger_ui_browser_tests::rig::{Rig, CONSOLE};

const SET: &str = "4f1c2b3a4d5e6f70";
const SHOWN: &str = "aaaaaaaaaaaaaaaa";
const AFTER_REPULL: &str = "bbbbbbbbbbbbbbbb";

async fn register_owner(tab: &Tab) {
    tab.until("the bootstrap door", "!document.getElementById('reg').hidden").await;
    tab.fill("#token", CONSOLE).await;
    tab.js("document.getElementById('role').value = 'admin'").await.unwrap();
    tab.until("the register button", "!!document.getElementById('register')").await;
    tab.js("document.getElementById('reg').open = true").await.unwrap();
    tab.fill("#user", "owner").await;
    tab.click("#register").await;
    tab.until("registered", holger_ui_browser_tests::browser::SIGNED_IN_OR_REGISTERED).await;
    if !tab.signed_in().await {
        tab.press("#login").await;
        tab.until("holger's session in this tab", holger_ui_browser_tests::browser::SIGNED_IN).await;
    }
}

/// The text of the first shown element `selector` (waits for it).
async fn shown_text(tab: &Tab, what: &str, selector: &str) -> String {
    let sel = serde_json::to_string(selector).unwrap();
    let v = tab.until(what, &format!("(document.querySelector({sel}) || {{}}).innerText || ''")).await;
    v.as_str().unwrap().to_owned()
}

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn a_set_vote_names_the_revision_the_page_showed() {
    let rig = Rig::start().await;
    rig.offer_set(SET, SHOWN);
    let browser = Browser::launch().await.expect("chromium starts");
    let owner = browser.tab().await.unwrap();
    owner.open(&rig.at("/login")).await.unwrap();
    register_owner(&owner).await;
    owner.open(&rig.at("/security")).await.unwrap();
    owner.until("the set's card", &format!("!!document.querySelector('[data-set=\"{SET}\"]')")).await;

    // A re-pull changes the set after the page drew it: the vote names what the page showed,
    // and holger refuses it by name; nothing is cast.
    rig.repull_set(AFTER_REPULL);
    owner.click_text("Approve the set").await;
    let refused = shown_text(&owner, "holger's refusal", ".depsets p.bad[role=alert]").await;
    assert!(
        refused.contains("[holger-depset-4]") && refused.contains("read the set again"),
        "the page shows holger's 409 words: {refused}"
    );
    assert!(rig.set_votes().is_empty(), "nothing was cast: {:?}", rig.set_votes());

    // The page reloaded the set after the answer: the vote names the revision it shows now.
    owner.until("the reloaded set", &format!("!!document.querySelector('[data-set=\"{SET}\"][data-revision=\"{AFTER_REPULL}\"]')")).await;
    owner.click_text("Approve the set").await;
    let taken = shown_text(&owner, "holger's answer", ".depsets p.good[role=status]").await;
    assert!(taken.contains("approved"), "{taken}");
    assert_eq!(rig.set_votes(), vec![("approve".to_string(), AFTER_REPULL.to_string())]);
}