mod support;
use std::fs;
use std::path::{Path, PathBuf};
use serde_json::Value;
use support::git_repo::GitRepo;
use support::pointbreak;
fn snapshot_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/review_documents")
}
fn canonical_repo_path(repo: &GitRepo) -> String {
repo.path()
.canonicalize()
.expect("canonicalize fixture repo path")
.to_string_lossy()
.into_owned()
}
fn normalize_hashes(text: &str) -> String {
replace_prefixed(text, "sha256:", "sha256:<h>", |rest| {
let hex: String = rest.chars().take(64).collect();
if hex.len() == 64
&& hex
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
{
Some(64)
} else {
None
}
})
}
fn normalize_timestamps(text: &str) -> String {
["occurredAt", "createdAt", "observedAt", "capturedAt"]
.into_iter()
.fold(text.to_owned(), |text, key| {
normalize_timestamp_field(&text, key)
})
}
fn normalize_timestamp_field(text: &str, key: &str) -> String {
let prefix = format!("\"{key}\":\"");
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(start) = rest.find(&prefix) {
let value_start = start + prefix.len();
out.push_str(&rest[..value_start]);
let value_and_rest = &rest[value_start..];
let Some(end) = value_and_rest.find('"') else {
out.push_str(value_and_rest);
return out;
};
let value = &value_and_rest[..end];
if pointbreak::session::parse_event_instant(value).is_some() {
out.push_str("unix-ms:<t>");
} else {
out.push_str(value);
}
rest = &value_and_rest[end..];
}
out.push_str(rest);
out
}
fn replace_prefixed(
text: &str,
prefix: &str,
replacement: &str,
match_len: impl Fn(&str) -> Option<usize>,
) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
while let Some(idx) = rest.find(prefix) {
out.push_str(&rest[..idx]);
let after_prefix = &rest[idx + prefix.len()..];
if let Some(token_len) = match_len(after_prefix) {
out.push_str(replacement);
let byte_len: usize = after_prefix
.chars()
.take(token_len)
.map(char::len_utf8)
.sum();
rest = &after_prefix[byte_len..];
} else {
out.push_str(prefix);
rest = after_prefix;
}
}
out.push_str(rest);
out
}
fn normalize_git_oid(text: &str, key: &str) -> String {
let prefix = format!("\"{key}\":\"");
replace_prefixed(text, &prefix, &format!("\"{key}\":\"<oid>"), |rest| {
let hex: String = rest.chars().take(40).collect();
let closes = rest.chars().nth(40) == Some('"');
if hex.len() == 40
&& closes
&& hex
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
{
Some(40)
} else {
None
}
})
}
fn normalize_worktree_root(text: &str) -> String {
replace_prefixed(
text,
"\"worktreeRoot\":\"",
"\"worktreeRoot\":\"<repo>",
|rest| {
let mut escaped = false;
let mut chars = 0usize;
for c in rest.chars() {
if escaped {
escaped = false;
chars += 1;
continue;
}
match c {
'\\' => {
escaped = true;
chars += 1;
}
'"' => return Some(chars),
_ => chars += 1,
}
}
None
},
)
}
fn normalize_producer_for_historical_snapshot(text: &str) -> String {
["shore", "pointbreak"]
.into_iter()
.fold(text.to_owned(), |text, producer| {
replace_prefixed(
&text,
&format!(r#""producer":{{"name":"{producer}","version":""#),
r#""producer":{"name":"shore","version":"<producerVersion>"#,
|rest| rest.find('"'),
)
})
}
fn normalize_cli_version(text: &str) -> String {
replace_prefixed(
text,
r#""cliVersion":""#,
r#""cliVersion":"<cliVersion>"#,
|rest| rest.find('"'),
)
}
fn normalize_build_identity(text: &str) -> String {
let text = replace_prefixed(
text,
r#""build":{"source":"git","commit":""#,
r#""build":{"source":"git","commit":"<buildCommit>"#,
|rest| rest.find('"'),
);
let text = replace_prefixed(
&text,
r#""commit":"<buildCommit>","describe":""#,
r#""commit":"<buildCommit>","describe":"<buildDescribe>"#,
|rest| rest.find('"'),
);
let text = replace_prefixed(
&text,
r#""build":{"source":"package","commit":null,"describe":""#,
r#""build":{"source":"package","commit":null,"describe":"<buildDescribe>"#,
|rest| rest.find('"'),
);
let text = text.replace(
r#""describe":"<buildDescribe>","dirty":true"#,
r#""describe":"<buildDescribe>","dirty":false"#,
);
text.replace(
r#""build":{"source":"git","commit":"<buildCommit>""#,
r#""build":{"source":"<buildSource>","commit":null"#,
)
.replace(
r#""build":{"source":"package","commit":null"#,
r#""build":{"source":"<buildSource>","commit":null"#,
)
}
fn normalize(raw: &str, repo_path: &str) -> String {
let text = raw.replace("\r\n", "\n");
let text = text.replace(repo_path, "<repo>");
let text = normalize_worktree_root(&text);
let text = normalize_hashes(&text);
let text = normalize_timestamps(&text);
let text = normalize_producer_for_historical_snapshot(&text);
let text = normalize_cli_version(&text);
let text = normalize_build_identity(&text);
let text = normalize_git_oid(&text, "commitOid");
let text = normalize_git_oid(&text, "treeOid");
let text = normalize_git_oid(&text, "headOid");
let text = normalize_git_oid(&text, "recordedHeadOid");
normalize_git_oid(&text, "currentTipOid")
}
#[test]
fn normalizer_masks_release_versions_without_masking_schema_version() {
let old = r#"{"version":1,"cliVersion":"0.5.0","writer":{"producer":{"name":"shore","version":"0.5.0"}}}"#;
let current = r#"{"version":1,"cliVersion":"0.6.0","writer":{"producer":{"name":"pointbreak","version":"0.6.0"}}}"#;
assert_eq!(normalize(old, "<absent>"), normalize(current, "<absent>"));
assert!(normalize(current, "<absent>").contains(r#""version":1"#));
}
#[test]
fn normalizer_masks_store_and_context_identity_hashes() {
let hash = "1".repeat(64);
let document = format!(
r#"{{"storeIdentity":"store:sha256:{hash}","contextIdentity":"context:sha256:{hash}"}}"#
);
let normalized = normalize(&document, "<absent>");
assert!(normalized.contains(r#""storeIdentity":"store:sha256:<h>""#));
assert!(normalized.contains(r#""contextIdentity":"context:sha256:<h>""#));
assert!(!normalized.contains(&hash));
}
#[test]
fn version_flag_remains_available() {
let output = pointbreak(["--version"]);
assert!(output.status.success());
assert!(
String::from_utf8(output.stdout)
.unwrap()
.starts_with("pointbreak ")
);
}
#[track_caller]
fn run_command(repo: &GitRepo, args: &[&str]) -> String {
let output = pointbreak(args);
assert!(
output.status.success(),
"command {args:?} failed\nstderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
let raw = String::from_utf8(output.stdout).expect("stdout is utf-8");
normalize(&raw, &canonical_repo_path(repo))
}
#[track_caller]
fn assert_snapshot(name: &str, normalized: &str) {
let path = snapshot_dir().join(format!("{name}.snap"));
if std::env::var_os("BLESS").is_some() {
fs::create_dir_all(snapshot_dir()).expect("create snapshot dir");
fs::write(&path, normalized).expect("write snapshot");
return;
}
let expected = fs::read_to_string(&path)
.unwrap_or_else(|error| {
panic!(
"missing snapshot {}: {error}. Re-run with BLESS=1 to generate it.",
path.display()
)
})
.replace("\r\n", "\n");
assert_eq!(
normalized,
expected,
"documented JSON for `{name}` drifted from the stored snapshot {}.\n\
If this change is intentional, re-run with BLESS=1 to regenerate.",
path.display()
);
}
#[test]
fn version_document_is_byte_stable() {
let repo = GitRepo::new();
let output = run_command(&repo, &["version"]);
assert_snapshot("version", &output);
}
fn fixture_repo() -> (GitRepo, String) {
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
let raw =
String::from_utf8(pointbreak(["capture", "--repo", repo.path().to_str().unwrap()]).stdout)
.expect("capture stdout is utf-8");
let value: Value = serde_json::from_str(&raw).expect("valid capture json");
let revision_id = value["revision"]["id"]
.as_str()
.expect("review unit id")
.to_owned();
(repo, revision_id)
}
fn repo_arg(repo: &GitRepo) -> String {
repo.path().to_str().unwrap().to_owned()
}
#[test]
fn review_documents_are_byte_stable() {
{
let repo = GitRepo::new();
repo.write("src/lib.rs", "pub fn value() -> u32 { 1 }\n");
repo.commit_all("base");
repo.write("src/lib.rs", "pub fn value() -> u32 { 2 }\n");
let out = run_command(&repo, &["capture", "--repo", &repo_arg(&repo)]);
assert_snapshot("capture", &out);
}
let (repo, unit) = fixture_repo();
let repo_path = repo_arg(&repo);
let observation_add = run_command(
&repo,
&[
"observation",
"add",
"--repo",
&repo_path,
"--track",
"agent:codex",
"--title",
"Observed change",
"--body",
"the return value changed",
],
);
assert_snapshot("observation_add", &observation_add);
let observation_list = run_command(
&repo,
&[
"observation",
"list",
"--repo",
&repo_path,
"--include-body",
],
);
assert_snapshot("observation_list", &observation_list);
let open_raw = pointbreak([
"input-request",
"open",
"--repo",
&repo_path,
"--track",
"agent:codex",
"--title",
"Need a decision",
"--reason",
"manual-decision-required",
"--body",
"should we ship this?",
]);
assert!(open_raw.status.success());
let open_value: Value =
serde_json::from_slice(&open_raw.stdout).expect("valid input-request open json");
let input_request_id = open_value["inputRequestId"]
.as_str()
.expect("input request id")
.to_owned();
let input_request_open = normalize(
&String::from_utf8(open_raw.stdout).unwrap(),
&canonical_repo_path(&repo),
);
assert_snapshot("input_request_open", &input_request_open);
let input_request_fetch = run_command(
&repo,
&[
"input-request",
"show",
&input_request_id,
"--repo",
&repo_path,
"--include-body",
],
);
assert_snapshot("input_request_fetch", &input_request_fetch);
let input_request_respond = run_command(
&repo,
&[
"input-request",
"respond",
&input_request_id,
"--repo",
&repo_path,
"--outcome",
"approved",
"--reason",
"looks good",
],
);
assert_snapshot("input_request_respond", &input_request_respond);
let input_request_list = run_command(
&repo,
&[
"input-request",
"list",
"--repo",
&repo_path,
"--status",
"all",
"--include-body",
],
);
assert_snapshot("input_request_list", &input_request_list);
let assessment_add = run_command(
&repo,
&[
"assessment",
"add",
"--repo",
&repo_path,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"ship it",
],
);
assert_snapshot("assessment_add", &assessment_add);
let assessment_show = run_command(
&repo,
&[
"assessment",
"show",
"--repo",
&repo_path,
"--include-summary",
],
);
assert_snapshot("assessment_show", &assessment_show);
let unit_show = run_command(
&repo,
&[
"revision",
"show",
&unit,
"--repo",
&repo_path,
"--include-body",
],
);
assert_snapshot("unit_show", &unit_show);
let unit_list = run_command(&repo, &["revision", "list", "--repo", &repo_path]);
assert_snapshot("unit_list", &unit_list);
let history = run_command(&repo, &["history", "--repo", &repo_path, "--include-body"]);
assert_snapshot("history", &history);
let validation_add = run_command(
&repo,
&[
"validation",
"add",
"--repo",
&repo_path,
"--track",
"agent:codex",
"--check-name",
"cargo test",
"--status",
"passed",
"--command",
"cargo test --all",
"--exit-code",
"0",
"--source-fingerprint",
"rev:sha256:1111111111111111111111111111111111111111111111111111111111111111",
"--summary",
"all tests passed",
"--started-at",
"2026-05-10T00:00:00Z",
"--completed-at",
"2026-05-10T00:01:00Z",
"--log-content-hash",
"sha256:2222222222222222222222222222222222222222222222222222222222222222",
],
);
assert_snapshot("validation_add", &validation_add);
let validation_list = run_command(
&repo,
&["validation", "list", "--repo", &repo_path, "--include-body"],
);
assert_snapshot("validation_list", &validation_list);
let attention_open = pointbreak([
"input-request",
"open",
"--repo",
&repo_path,
"--track",
"human:kevin",
"--title",
"Runtime trace required",
"--reason",
"insufficient-evidence",
]);
assert!(attention_open.status.success());
let attention_list = run_command(
&repo,
&[
"attention",
"list",
"--repo",
&repo_path,
"--format",
"json",
],
);
assert_snapshot("attention_list", &attention_list);
}