Files
clawmates/tools/bundler/tests/bundle.rs
T
Omar SobhandClaude Fable 5 add4f79fed Rebrand: TeamClaw -> Clawmates (clawmates.work)
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.

- Display brand: Clawmates (manifest, titles, hero, login/rail logo
  'clawmates'); default host app.clawmates.work; registry
  ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
  clawmates-server/broker/bundler; images clawmates/*; env prefix
  CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
  clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
  resources; db names clawmates*; sockets /run/clawmates; cookie
  cm_session; kind cluster clawmates-test; seccomp node profile
  clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
  spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
  test server clawmates-test-pg, kind cluster recreated with image +
  profile, compose images rebuilt under clawmates/*

Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 12:31:25 -05:00

114 lines
3.6 KiB
Rust

use std::fs;
use std::path::PathBuf;
use clawmates_bundler::{assemble, generate_keypair, verify, BundleError};
struct Fixture {
root: PathBuf,
public_key: PathBuf,
bundle: PathBuf,
}
fn fixture() -> Fixture {
let root = std::env::temp_dir().join(format!("tc-bundle-{}", uuid::Uuid::now_v7()));
fs::create_dir_all(&root).unwrap();
let private_key = root.join("release.key");
let public_key = root.join("release.pub");
generate_keypair(&private_key, &public_key).unwrap();
fs::write(root.join("server.tar"), b"pretend image bytes").unwrap();
fs::write(root.join("compose.yml"), b"services: {}").unwrap();
fs::write(root.join("0001_init.sql"), b"CREATE TABLE t ();").unwrap();
let bundle = root.join("bundle");
assemble(
&bundle,
"1.2.3",
&[
(root.join("server.tar"), "images/server.tar".into()),
(
root.join("compose.yml"),
"compose/docker-compose.yml".into(),
),
(
root.join("0001_init.sql"),
"migrations/0001_init.sql".into(),
),
],
&private_key,
)
.unwrap();
Fixture {
root,
public_key,
bundle,
}
}
#[test]
fn assembled_bundles_verify_offline() {
let fx = fixture();
// 3 artifacts + manifest.json.
assert_eq!(verify(&fx.bundle, &fx.public_key).unwrap(), 4);
let manifest: serde_json::Value =
serde_json::from_str(&fs::read_to_string(fx.bundle.join("manifest.json")).unwrap())
.unwrap();
assert_eq!(manifest["version"], "1.2.3");
assert_eq!(manifest["artifacts"].as_array().unwrap().len(), 3);
}
#[test]
fn a_tampered_artifact_is_detected() {
let fx = fixture();
fs::write(fx.bundle.join("images/server.tar"), b"EVIL image bytes").unwrap();
let err = verify(&fx.bundle, &fx.public_key).unwrap_err();
assert!(matches!(err, BundleError::ChecksumMismatch(p) if p == "images/server.tar"));
}
#[test]
fn a_tampered_checksum_list_fails_the_signature() {
let fx = fixture();
// Attacker edits both the artifact AND its checksum line — the
// signature over checksums.txt catches it.
let listing = fs::read_to_string(fx.bundle.join("checksums.txt")).unwrap();
fs::write(
fx.bundle.join("checksums.txt"),
listing.replace("images/server.tar", "images/sneaky.tar"),
)
.unwrap();
let err = verify(&fx.bundle, &fx.public_key).unwrap_err();
assert!(matches!(err, BundleError::BadSignature));
}
#[test]
fn the_wrong_public_key_is_refused() {
let fx = fixture();
let other_private = fx.root.join("other.key");
let other_public = fx.root.join("other.pub");
generate_keypair(&other_private, &other_public).unwrap();
let err = verify(&fx.bundle, &other_public).unwrap_err();
assert!(matches!(err, BundleError::BadSignature));
}
#[test]
fn a_missing_artifact_is_reported() {
let fx = fixture();
fs::remove_file(fx.bundle.join("migrations/0001_init.sql")).unwrap();
let err = verify(&fx.bundle, &fx.public_key).unwrap_err();
assert!(matches!(err, BundleError::Missing(p) if p.contains("0001_init.sql")));
}
#[test]
fn the_public_key_rederives_from_the_signing_key() {
let fx = fixture();
let derived = fx.root.join("derived.pub");
clawmates_bundler::derive_public_key(&fx.root.join("release.key"), &derived).unwrap();
assert_eq!(
fs::read_to_string(&derived).unwrap(),
fs::read_to_string(&fx.public_key).unwrap()
);
// And it verifies the bundle, same as the original.
assert_eq!(verify(&fx.bundle, &derived).unwrap(), 4);
}