test(bootstrap): cover CP1252 stderr decode and UTF-8 BOM cache writes

Locks the #67193 invariants: localized PowerShell error bytes survive
decode_console_bytes / read_decoded_line (including CP1252-only 0x91/0x92
punctuation), cached .ps1 files get a single UTF-8 BOM for Windows
PowerShell 5.1 -File, .sh stays BOM-less, and mutable branch caches plan
a refresh with stale-cache fallback.
This commit is contained in:
HexLab98 2026-07-19 13:45:07 +07:00 committed by Teknium
parent 4ce1994159
commit acee4f25c7
2 changed files with 106 additions and 0 deletions

View file

@ -349,4 +349,58 @@ mod tests {
assert_eq!(sanitize_ref("main"), "main");
assert_eq!(sanitize_ref("release/1.2.3"), "release_1.2.3");
}
#[test]
fn prepare_cached_ps1_prefixes_utf8_bom() {
let out = prepare_cached_script_bytes(ScriptKind::Ps1, b"Write-Host hi\n");
assert!(out.starts_with(UTF8_BOM), "cached .ps1 must start with UTF-8 BOM");
assert_eq!(&out[UTF8_BOM.len()..], b"Write-Host hi\n");
}
#[test]
fn prepare_cached_ps1_does_not_double_bom() {
let mut already = UTF8_BOM.to_vec();
already.extend_from_slice(b"x");
let out = prepare_cached_script_bytes(ScriptKind::Ps1, &already);
assert_eq!(out, already);
assert_eq!(out.windows(3).filter(|w| *w == UTF8_BOM).count(), 1);
}
#[test]
fn prepare_cached_sh_stays_bomless() {
let out = prepare_cached_script_bytes(ScriptKind::Sh, b"#!/bin/bash\n");
assert!(!out.starts_with(UTF8_BOM));
assert_eq!(out, b"#!/bin/bash\n");
}
#[test]
fn commit_pins_are_immutable_branch_pins_are_not() {
// Mirrors the resolve() immutable decision: SHA pins may reuse cache
// forever; branch pins must refresh so Retry cannot keep a bad script.
assert!(is_valid_commit("02d26981d3d4ad50e142399b8476f59ad5953ff0"));
assert!(!is_valid_commit("main"));
assert!(!is_valid_commit("release/1.2.3"));
}
#[test]
fn existing_branch_cache_plans_refresh_with_stale_fallback() {
// Resolver-level: a prior install-main.ps1 must not short-circuit
// Retry — mutable pins refresh, and only fall back if download fails.
assert_eq!(
cache_plan(/*immutable=*/ false, /*cached_exists=*/ true),
CachePlan::Fetch { stale_ok: true }
);
assert_eq!(
cache_plan(/*immutable=*/ true, /*cached_exists=*/ true),
CachePlan::Reuse
);
assert_eq!(
cache_plan(/*immutable=*/ false, /*cached_exists=*/ false),
CachePlan::Fetch { stale_ok: false }
);
assert_eq!(
cache_plan(/*immutable=*/ true, /*cached_exists=*/ false),
CachePlan::Fetch { stale_ok: false }
);
}
}

View file

@ -447,4 +447,56 @@ info line
"unexpected powershell path: {normalized}"
);
}
#[test]
fn decode_console_bytes_keeps_valid_utf8() {
assert_eq!(decode_console_bytes("café — ok".as_bytes()), "café — ok");
}
#[test]
fn decode_console_bytes_preserves_cp1252_portuguese_error() {
// "Não foi fornecido o terminador..." as Windows PowerShell 5.1 emits
// under CP1252 (0xE3 = ã). BufReader::lines() previously failed here
// with "stream did not contain valid UTF-8" and the UI only showed "No".
let bytes: &[u8] = b"N\xE3o foi fornecido o terminador";
assert_eq!(decode_console_bytes(bytes), "Não foi fornecido o terminador");
}
#[test]
fn decode_console_bytes_maps_cp1252_only_punctuation() {
// 0x91/0x92 are curly quotes in Windows-1252, but C1 controls under
// Latin-1 (`b as char`). This locks the real CP1252 fallback.
let bytes: &[u8] = b"say \x91hi\x92";
assert_eq!(decode_console_bytes(bytes), "say \u{2018}hi\u{2019}");
assert_ne!(
decode_console_bytes(bytes),
bytes.iter().map(|&b| b as char).collect::<String>(),
"Latin-1 byte mapping must not be used for the 0x80..=0x9F range"
);
}
#[tokio::test]
async fn read_decoded_line_survives_non_utf8_and_crlf() {
let data: &[u8] = b"N\xE3o erro\r\nnext\n";
let mut reader = BufReader::new(data);
let mut buf = Vec::new();
assert_eq!(
read_decoded_line(&mut reader, &mut buf)
.await
.unwrap()
.as_deref(),
Some("Não erro")
);
assert_eq!(
read_decoded_line(&mut reader, &mut buf)
.await
.unwrap()
.as_deref(),
Some("next")
);
assert!(read_decoded_line(&mut reader, &mut buf)
.await
.unwrap()
.is_none());
}
}