Files
relicario/crates/relicario-server/tests/org_hook.rs
adlee-was-taken 2dd5d79f36 refactor(server): fold in PM review notes on classify_path
- classify_path now Rejects a collection slug containing '.' (mirrors
  OrgCollections::validate, plan L317, and item_path's documented contract,
  plan L990). Unreachable today since git normalizes './' away, but keeps the
  pre-receive hook self-defensive against path traversal.
- Rename test item_write_nested_slug_takes_leading_segment_only ->
  item_write_nested_slug_is_rejected (it asserts Rejected; old name misled).
- Add dotted_slug_is_rejected covering the new guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5brcDrT35r5GaJySXD5ja
2026-06-20 00:04:39 -04:00

82 lines
2.5 KiB
Rust

// Integration tests for relicario-server org-hook path classification.
use relicario_server::{classify_path, PathClass};
#[test]
fn protected_files_are_classified_protected() {
assert_eq!(classify_path("members.json"), PathClass::Protected);
assert_eq!(classify_path("collections.json"), PathClass::Protected);
assert_eq!(classify_path("org.json"), PathClass::Protected);
}
#[test]
fn item_write_yields_collection_slug() {
assert_eq!(
classify_path("items/prod/a1b2c3d4e5f6a1b2.enc"),
PathClass::Item { collection: "prod".to_string() }
);
}
#[test]
fn item_write_nested_slug_is_rejected() {
// Slugs cannot contain '/', so a path with extra segments is malformed → Rejected.
assert_eq!(
classify_path("items/prod/sub/x.enc"),
PathClass::Rejected("items path must be items/<slug>/<id>.enc".to_string())
);
}
#[test]
fn key_blobs_and_manifest_are_unrestricted() {
// keys/<id>.enc and manifest.enc are written by org operations; the SIGNATURE
// check (every commit must be signed by a current member) is the gate for them.
assert_eq!(classify_path("keys/a1b2c3d4e5f6a1b2.enc"), PathClass::Unrestricted);
assert_eq!(classify_path("manifest.enc"), PathClass::Unrestricted);
}
#[test]
fn items_without_slug_segment_are_rejected() {
// Flat items/<id>.enc (the OLD, now-removed layout) is no longer valid.
assert_eq!(
classify_path("items/a1b2c3d4e5f6a1b2.enc"),
PathClass::Rejected("items path must be items/<slug>/<id>.enc".to_string())
);
}
#[test]
fn empty_slug_segment_is_rejected() {
assert_eq!(
classify_path("items//x.enc"),
PathClass::Rejected("empty collection slug in items path".to_string())
);
}
#[test]
fn dotted_slug_is_rejected() {
// Defense-in-depth (mirrors OrgCollections::validate): a slug containing '.'
// — e.g. a ".."/"." path-traversal attempt — is rejected.
assert_eq!(
classify_path("items/../x.enc"),
PathClass::Rejected("invalid collection slug: \"..\"".to_string())
);
}
use relicario_server::extract_schema_version;
#[test]
fn extract_schema_version_reads_field() {
let json = r#"{ "schema_version": 3, "members": [] }"#;
assert_eq!(extract_schema_version(json).unwrap(), 3);
}
#[test]
fn extract_schema_version_errors_on_missing_field() {
let json = r#"{ "members": [] }"#;
assert!(extract_schema_version(json).is_err());
}
#[test]
fn extract_schema_version_errors_on_garbage() {
assert!(extract_schema_version("not json").is_err());
}