tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1378Shell 129Rust 79Markdown 732463 Definiciones

⎇ 9be7bdd70751eec8a1b0b77b7440bd68b89ead65 crates/hammer-cli/tests/compat_gate.rs Rust · 77 líneas · 3.1 KB Historia Quién tocó qué crudo

H4c: superficies OBSERVADAS — colisión de fichero en `install` sin declarar slots (SDD 15 §H4) 9be7bdd70 · sergio ·

1//! H4c e2e: el gate de **colisión de fichero observada** en `hammer install` real.
2//!
3//! Reproduce el caso "logo" a nivel de fichero, sin declarar `slots`: dos paquetes escriben el
4//! mismo path. El primero ya está instalado (pre-seed de la DB); al instalar el segundo, el gate
5//! —que lee los paths que el `.swm` escribirá y los compara con lo instalado— debe ABORTAR antes
6//! de tocar nada. Con `--force-slots` la elección se respeta y el fichero se escribe.
7
8use std::process::Command;
9
10use base64::{engine::general_purpose::STANDARD, Engine as _};
11use hammer_core::ArtifactHash;
12
13#[test]
14fn install_aborta_ante_colision_de_fichero_y_force_slots_la_supera() {
15    let tmp = tempfile::tempdir().unwrap();
16    let repo = tmp.path().join("repo");
17    std::fs::create_dir_all(&repo).unwrap();
18    let prefix = tmp.path().join("prefix");
19    let db = tmp.path().join("installed.json");
20
21    // Pre-seed: 'tema-claro' YA posee /share/logo.png.
22    std::fs::write(
23        &db,
24        r#"{"packages":[{"name":"tema-claro","version":"1","files":["/share/logo.png"]}]}"#,
25    )
26    .unwrap();
27
28    // Paquete entrante 'tema-oscuro' = un file_drop que escribe el MISMO path.
29    let content = b"logo-oscuro";
30    // apply_file_drop verifica con `of_inputs` (length-prefixed), no `of_bytes` — mismamos eso.
31    let hash = ArtifactHash::of_inputs(&[content.as_slice()]);
32    let b64 = STANDARD.encode(content);
33    let swm = format!(
34        "swm_version: 1\nbase:\n  distro_version: dev\n  pins: {{}}\nmutations:\n\
35         - type: file_drop\n  path: /share/logo.png\n  content_hash: {}\n  content_b64: {}\n",
36        hash.as_str(),
37        b64
38    );
39    std::fs::write(repo.join("tema-oscuro-1.swm"), swm).unwrap();
40    let index = r#"{"packages":[{"name":"tema-oscuro","version":"1","file":"tema-oscuro-1.swm","distro_version":"dev","deps":[]}]}"#;
41    std::fs::write(repo.join("index.json"), index).unwrap();
42
43    let bin = env!("CARGO_BIN_EXE_hammer");
44    let base_args = [
45        "install",
46        "tema-oscuro",
47        "--repo",
48        repo.to_str().unwrap(),
49        "--db",
50        db.to_str().unwrap(),
51        "--prefix",
52        prefix.to_str().unwrap(),
53    ];
54
55    // 1) Sin --force-slots: colisión ⇒ aborta, y el fichero NO se escribe (aborta pre-hidratación).
56    let out = Command::new(bin).args(base_args).output().unwrap();
57    assert!(!out.status.success(), "debía abortar por colisión de fichero");
58    let err = String::from_utf8_lossy(&out.stderr);
59    assert!(err.contains("COLISIÓN de fichero"), "stderr inesperado: {err}");
60    assert!(
61        !prefix.join("share/logo.png").exists(),
62        "no debía escribir el fichero al abortar"
63    );
64
65    // 2) Con --force-slots: la elección se respeta y el fichero se escribe.
66    let out = Command::new(bin)
67        .args(base_args)
68        .arg("--force-slots")
69        .output()
70        .unwrap();
71    let err = String::from_utf8_lossy(&out.stderr);
72    assert!(out.status.success(), "con --force-slots debía instalar. stderr: {err}");
73    assert!(
74        prefix.join("share/logo.png").exists(),
75        "el fichero debía escribirse con --force-slots. stderr: {err}"
76    );
77}