mod support;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use pointbreak::model::ObjectId;
use pointbreak::session::{
ArtifactKind, ArtifactRef, ImportArtifactOptions, export_artifact, import_artifact,
read_object_artifact, referenced_artifacts,
};
use serde_json::Value;
use support::git_repo::GitRepo;
use support::shore;
struct LinkedFixture {
main: GitRepo,
_worktree_parent: tempfile::TempDir,
seed: PathBuf,
reader: PathBuf,
seed_revision_id: String,
seed_snapshot_id: String,
seed_object_artifact_content_hash: String,
}
impl LinkedFixture {
fn new() -> Self {
let main = GitRepo::new();
main.write("README.md", "base\n");
main.commit_all("base");
let worktree_parent = tempfile::tempdir().expect("create worktree parent");
let seed = worktree_parent.path().join("seed");
add_worktree(main.path(), &seed, "seed");
let reader = worktree_parent.path().join("reader");
add_worktree(main.path(), &reader, "reader");
let mut fixture = Self {
main,
_worktree_parent: worktree_parent,
seed,
reader,
seed_revision_id: String::new(),
seed_snapshot_id: String::new(),
seed_object_artifact_content_hash: String::new(),
};
fs::write(fixture.seed.join("README.md"), "changed in seed\n").unwrap();
let capture = fixture.capture(&fixture.seed);
fixture.seed_revision_id = capture["revision"]["id"]
.as_str()
.expect("capture has review unit id")
.to_owned();
fixture.seed_snapshot_id = capture["revision"]["objectId"]
.as_str()
.expect("capture has snapshot id")
.to_owned();
fixture.seed_object_artifact_content_hash =
capture["revision"]["objectArtifactContentHash"]
.as_str()
.expect("capture has object artifact content hash")
.to_owned();
fixture
}
fn capture(&self, worktree: &Path) -> Value {
run_shore_json(&["capture", "--repo", worktree.to_str().unwrap()])
}
fn observation_add(&self, worktree: &Path, revision_id: &str, body: &str) -> Value {
run_shore_json(&[
"observation",
"add",
"--repo",
worktree.to_str().unwrap(),
"--revision",
revision_id,
"--track",
"agent:test-fixture",
"--title",
"linked body artifact",
"--body",
body,
])
}
fn linked_store_dir(&self) -> PathBuf {
self.main.path().join(".git/shore")
}
fn history_json(&self, worktree: &Path, include_body: bool) -> Value {
let mut args = vec!["history", "--repo", worktree.to_str().unwrap()];
if include_body {
args.push("--include-body");
}
run_shore_json(&args)
}
fn unit_show_json(&self, worktree: &Path, revision_id: &str) -> Value {
run_shore_json(&[
"revision",
"show",
revision_id,
"--repo",
worktree.to_str().unwrap(),
"--include-body",
])
}
fn seed_full_facts(&self, body: &str) -> String {
self.observation_add(&self.seed, &self.seed_revision_id, body);
let seed = self.seed.to_str().unwrap();
let opened = run_shore_json(&[
"input-request",
"open",
"--repo",
seed,
"--track",
"agent:test-fixture",
"--title",
"Need approval",
"--reason",
"manual-decision-required",
"--body",
"approve this path?",
]);
run_shore_json(&[
"assessment",
"add",
"--repo",
seed,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"ship it",
]);
run_shore_json(&[
"validation",
"add",
"--repo",
seed,
"--track",
"agent:test-fixture",
"--check-name",
"cargo test",
"--status",
"passed",
]);
opened["inputRequestId"]
.as_str()
.expect("input request open returns id")
.to_owned()
}
fn respond_input_request(&self, worktree: &Path, input_request_id: &str) -> Value {
run_shore_json(&[
"input-request",
"respond",
input_request_id,
"--repo",
worktree.to_str().unwrap(),
"--outcome",
"approved",
"--reason",
"approved locally",
])
}
fn remove_seed(&self) {
run_git_os(
self.main.path(),
[
OsString::from("worktree"),
OsString::from("remove"),
OsString::from("--force"),
self.seed.as_os_str().to_owned(),
],
);
assert!(!self.seed.exists());
}
fn unit_list_json(&self, worktree: &Path) -> Value {
run_shore_json(&["revision", "list", "--repo", worktree.to_str().unwrap()])
}
}
fn populated_fixture_with_deleted_seed(body: &str) -> (LinkedFixture, String) {
let fixture = LinkedFixture::new();
let input_request_id = fixture.seed_full_facts(body);
fixture.respond_input_request(&fixture.seed, &input_request_id);
fixture.remove_seed();
(fixture, input_request_id)
}
fn assert_no_deleted_path_in_diagnostics(fixture: &LinkedFixture, json: &Value) {
let diagnostics = json["diagnostics"].to_string();
assert!(
!diagnostics.contains(fixture.seed.to_str().unwrap()),
"diagnostics mention the deleted worktree path: {diagnostics}"
);
}
#[test]
fn deleted_worktree_unit_list_lists_unit() {
let (fixture, _) = populated_fixture_with_deleted_seed("m1");
let json = fixture.unit_list_json(&fixture.reader);
assert_eq!(json["revisionCount"], 1);
assert_eq!(
json["entries"][0]["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_unit_show_renders_composite_with_snapshot() {
let body = "m".repeat(5000);
let (fixture, _) = populated_fixture_with_deleted_seed(&body);
let json = fixture.unit_show_json(&fixture.reader, &fixture.seed_revision_id);
assert_eq!(json["summary"]["observationCount"], 1);
assert_eq!(json["summary"]["inputRequestCount"], 1);
assert_eq!(json["summary"]["assessmentCount"], 1);
assert_eq!(json["summary"]["validationCheckCount"], 1);
assert!(json["summary"]["snapshotRowCount"].as_u64().unwrap() > 0);
assert_eq!(json["observations"][0]["body"], Value::String(body));
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_history_renders_timeline_with_bodies() {
let body = "n".repeat(5000);
let (fixture, _) = populated_fixture_with_deleted_seed(&body);
let json = fixture.history_json(&fixture.reader, true);
assert!(json["eventCount"].as_u64().unwrap() > 0);
assert!(
json.to_string().contains(&body),
"hydrated observation body loads from the linked store"
);
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_observation_list_renders_with_hydrated_body() {
let body = "p".repeat(5000);
let (fixture, _) = populated_fixture_with_deleted_seed(&body);
let json = run_shore_json(&[
"observation",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--include-body",
]);
assert_eq!(json["observations"].as_array().unwrap().len(), 1);
assert_eq!(json["observations"][0]["body"], Value::String(body));
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_input_request_list_renders_with_response() {
let (fixture, input_request_id) = populated_fixture_with_deleted_seed("m5");
let json = run_shore_json(&[
"input-request",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--status",
"all",
]);
assert_eq!(json["inputRequests"].as_array().unwrap().len(), 1);
assert_eq!(
json["inputRequests"][0]["id"],
Value::String(input_request_id)
);
assert_eq!(
json["inputRequests"][0]["responses"]
.as_array()
.unwrap()
.len(),
1
);
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_assessment_show_renders() {
let (fixture, _) = populated_fixture_with_deleted_seed("m6");
let json = run_shore_json(&[
"assessment",
"show",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
]);
assert_eq!(json["assessments"].as_array().unwrap().len(), 1);
assert_eq!(json["assessments"][0]["assessment"], "accepted");
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn deleted_worktree_validation_list_renders() {
let (fixture, _) = populated_fixture_with_deleted_seed("m7");
let json = run_shore_json(&[
"validation",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
]);
assert_eq!(json["validationChecks"].as_array().unwrap().len(), 1);
assert_no_deleted_path_in_diagnostics(&fixture, &json);
}
#[test]
fn linked_reads_agree_on_event_set_hash_across_surfaces() {
let fixture = LinkedFixture::new();
fixture.seed_full_facts("short body");
let reader_units = fixture.unit_list_json(&fixture.reader);
let reader_history = fixture.history_json(&fixture.reader, false);
let seed_units = fixture.unit_list_json(&fixture.seed);
let seed_history = fixture.history_json(&fixture.seed, false);
let hash = event_set_hash(&reader_units);
assert!(hash.starts_with("sha256:"));
assert_eq!(event_set_hash(&reader_history), hash);
assert_eq!(event_set_hash(&seed_units), hash);
assert_eq!(event_set_hash(&seed_history), hash);
assert_eq!(reader_units["eventCount"], reader_history["eventCount"]);
assert_eq!(reader_units["eventCount"], seed_units["eventCount"]);
}
#[test]
fn reader_capture_is_immediately_visible_via_write_through() {
let fixture = LinkedFixture::new();
let units = fixture.unit_list_json(&fixture.reader);
assert_eq!(units["revisionCount"], 1);
let before_hash = event_set_hash(&units).to_owned();
fs::write(fixture.reader.join("README.md"), "changed in reader\n").unwrap();
let local_capture = fixture.capture(&fixture.reader);
let local_unit_id = local_capture["revision"]["id"].as_str().unwrap().to_owned();
let units = fixture.unit_list_json(&fixture.reader);
let history = fixture.history_json(&fixture.reader, false);
assert_eq!(units["revisionCount"], 2);
assert!(units["entries"].to_string().contains(&local_unit_id));
let advanced_hash = event_set_hash(&units);
assert_ne!(advanced_hash, before_hash);
assert_eq!(event_set_hash(&history), advanced_hash);
}
#[test]
fn reader_capture_file_target_observation_resolves_artifact() {
let fixture = LinkedFixture::new();
fs::write(fixture.reader.join("README.md"), "changed in reader\n").unwrap();
let capture = fixture.capture(&fixture.reader);
let local_unit_id = capture["revision"]["id"]
.as_str()
.expect("local capture has review unit id")
.to_owned();
let json = run_shore_json(&[
"observation",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&local_unit_id,
"--track",
"agent:test-fixture",
"--title",
"file targeted on local capture",
"--file",
"README.md",
]);
assert_eq!(json["target"]["kind"], "file");
assert_eq!(json["target"]["filePath"], "README.md");
assert_eq!(json["target"]["revisionId"], Value::String(local_unit_id));
}
#[test]
fn linked_reader_attaches_observation_to_linked_only_unit() {
let fixture = LinkedFixture::new();
let result = fixture.observation_add(
&fixture.reader,
&fixture.seed_revision_id,
"cross-worktree note",
);
assert_eq!(result["eventsCreated"], 1);
assert!(
result["observationId"].as_str().is_some(),
"result carries an observation id: {result}"
);
}
#[test]
fn linked_reader_opens_input_request_against_linked_only_unit() {
let fixture = LinkedFixture::new();
let result = run_shore_json(&[
"input-request",
"open",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"agent:test-fixture",
"--title",
"cross-worktree question",
"--reason",
"manual-decision-required",
"--body",
"approve?",
]);
assert_eq!(result["eventsCreated"], 1);
assert!(result["inputRequestId"].as_str().is_some());
}
#[test]
fn linked_reader_opens_input_request_with_observation_ref_target() {
let fixture = LinkedFixture::new();
let observation = fixture.observation_add(
&fixture.seed,
&fixture.seed_revision_id,
"observation to reference",
);
let observation_id = observation["observationId"]
.as_str()
.expect("seed observation has an id")
.to_owned();
let result = run_shore_json(&[
"input-request",
"open",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--observation",
&observation_id,
"--track",
"agent:test-fixture",
"--title",
"question about that observation",
"--reason",
"manual-decision-required",
"--body",
"is this right?",
]);
assert_eq!(result["eventsCreated"], 1);
assert_eq!(result["target"]["kind"], "observation");
assert_eq!(
result["target"]["observationId"],
Value::String(observation_id)
);
}
#[test]
fn linked_reader_responds_to_linked_only_input_request() {
let fixture = LinkedFixture::new();
let request_id = fixture.seed_full_facts("seed body");
let result = fixture.respond_input_request(&fixture.reader, &request_id);
assert_eq!(result["eventsCreated"], 1);
assert_eq!(result["outcome"], "approved");
}
#[test]
fn linked_reader_respond_copies_request_event_target_fields() {
let fixture = LinkedFixture::new();
let request_id = fixture.seed_full_facts("seed body");
fixture.respond_input_request(&fixture.reader, &request_id);
let events = read_store_events(&fixture.linked_store_dir());
let response = events
.iter()
.find(|event| {
event.event_type == pointbreak::session::event::EventType::InputRequestResponded
})
.expect("the response event is in the linked store");
let response_payload: pointbreak::session::event::InputRequestRespondedPayload =
serde_json::from_value(response.payload.clone()).unwrap();
assert_eq!(
response_payload.revision_id.as_ref().map(|id| id.as_str()),
Some(fixture.seed_revision_id.as_str())
);
assert_eq!(
response.target.track_id.as_ref().map(|id| id.as_str()),
Some("agent:test-fixture")
);
}
#[test]
fn linked_reader_records_assessment_on_linked_only_unit() {
let fixture = LinkedFixture::new();
let result = run_shore_json(&[
"assessment",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"ship it",
]);
assert_eq!(result["eventsCreated"], 1);
assert!(result["assessmentId"].as_str().is_some());
}
#[test]
fn linked_reader_assessment_relates_linked_only_observation() {
let fixture = LinkedFixture::new();
let observation = fixture.observation_add(
&fixture.seed,
&fixture.seed_revision_id,
"observation to relate",
);
let observation_id = observation["observationId"]
.as_str()
.expect("seed observation id")
.to_owned();
let result = run_shore_json(&[
"assessment",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"looks good",
"--related-observation",
&observation_id,
]);
assert_eq!(result["eventsCreated"], 1);
}
#[test]
fn linked_reader_assessment_replaces_linked_only_assessment() {
let fixture = LinkedFixture::new();
let seed_assessment = run_shore_json(&[
"assessment",
"add",
"--repo",
fixture.seed.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"first pass",
]);
let assessment_id = seed_assessment["assessmentId"]
.as_str()
.expect("seed assessment id")
.to_owned();
let result = run_shore_json(&[
"assessment",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"human:kevin",
"--assessment",
"needs-changes",
"--summary",
"second pass",
"--replaces",
&assessment_id,
]);
assert_eq!(result["eventsCreated"], 1);
}
#[test]
fn linked_reader_records_validation_on_linked_only_unit() {
let fixture = LinkedFixture::new();
let result = run_shore_json(&[
"validation",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--track",
"agent:test-fixture",
"--check-name",
"cargo test",
"--status",
"passed",
]);
assert_eq!(result["eventsCreated"], 1);
}
#[test]
fn linked_fact_writes_land_in_linked_store_not_worktree_local() {
let fixture = LinkedFixture::new();
let request_id = fixture.seed_full_facts("seed body");
let linked_before = event_file_names(&fixture.linked_store_dir());
let reader = fixture.reader.to_str().unwrap();
let unit = fixture.seed_revision_id.as_str();
fixture.observation_add(&fixture.reader, unit, "cross-worktree note");
run_shore_json(&[
"input-request",
"open",
"--repo",
reader,
"--revision",
unit,
"--track",
"agent:test-fixture",
"--title",
"q",
"--reason",
"manual-decision-required",
"--body",
"?",
]);
run_shore_json(&[
"assessment",
"add",
"--repo",
reader,
"--revision",
unit,
"--track",
"human:kevin",
"--assessment",
"accepted",
"--summary",
"ok",
]);
run_shore_json(&[
"validation",
"add",
"--repo",
reader,
"--revision",
unit,
"--track",
"agent:test-fixture",
"--check-name",
"cargo test",
"--status",
"passed",
]);
fixture.respond_input_request(&fixture.reader, &request_id);
let linked_after = event_file_names(&fixture.linked_store_dir());
assert!(
linked_after.len() > linked_before.len(),
"linked store gained the write-through fact events: before={} after={}",
linked_before.len(),
linked_after.len()
);
assert!(
event_file_names(&fixture.reader.join(".shore/data")).is_empty(),
"reader worktree-local store received no fact events in linked mode"
);
}
#[test]
fn linked_fact_write_state_json_is_orphan_free() {
let fixture = LinkedFixture::new();
fixture.observation_add(
&fixture.reader,
&fixture.seed_revision_id,
"cross-worktree note",
);
let bytes = fs::read(fixture.linked_store_dir().join("state.json"))
.expect("read clone-local state.json");
let state: Value = serde_json::from_slice(&bytes).expect("state.json is json");
assert!(state["observationCount"].as_u64().unwrap() >= 1);
assert!(
!state_diagnostic_codes(&state)
.iter()
.any(|code| code.contains("orphan")),
"diagnostics: {}",
state["diagnostics"]
);
}
#[test]
fn linked_fact_write_does_not_copy_object_artifacts_to_linked_store() {
let fixture = LinkedFixture::new();
fs::write(fixture.reader.join("README.md"), "changed in reader\n").unwrap();
let capture = fixture.capture(&fixture.reader);
let local_unit = capture["revision"]["id"].as_str().unwrap().to_owned();
let snapshots_before = object_artifact_names(&fixture.linked_store_dir());
run_shore_json(&[
"observation",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&local_unit,
"--track",
"agent:test-fixture",
"--title",
"t",
"--file",
"README.md",
]);
let snapshots_after = object_artifact_names(&fixture.linked_store_dir());
assert_eq!(
snapshots_before, snapshots_after,
"no object artifacts copied to the linked store by a write"
);
}
fn event_file_names(store_dir: &Path) -> Vec<String> {
json_file_names(&store_dir.join("events"))
}
fn read_store_events(store_dir: &Path) -> Vec<pointbreak::session::event::ShoreEvent> {
let events_dir = store_dir.join("events");
let mut entries: Vec<PathBuf> = match fs::read_dir(&events_dir) {
Ok(read_dir) => read_dir
.filter_map(|entry| entry.ok())
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|ext| ext == "json"))
.collect(),
Err(_) => Vec::new(),
};
entries.sort();
entries
.iter()
.map(|path| {
let bytes = fs::read(path).expect("read event file");
serde_json::from_slice(&bytes).expect("event file is a ShoreEvent")
})
.collect()
}
fn object_artifact_names(store_dir: &Path) -> Vec<String> {
json_file_names(&store_dir.join("artifacts/objects"))
}
fn json_file_names(dir: &Path) -> Vec<String> {
let mut names: Vec<String> = match fs::read_dir(dir) {
Ok(entries) => entries
.filter_map(|entry| entry.ok())
.filter_map(|entry| entry.file_name().into_string().ok())
.filter(|name| name.ends_with(".json"))
.collect(),
Err(_) => Vec::new(),
};
names.sort();
names
}
fn state_diagnostic_codes(state: &Value) -> Vec<String> {
state["diagnostics"]
.as_array()
.map(|diagnostics| {
diagnostics
.iter()
.filter_map(|diagnostic| diagnostic["code"].as_str().map(str::to_owned))
.collect()
})
.unwrap_or_default()
}
#[test]
fn cross_worktree_fact_is_immediately_visible_via_write_through() {
let fixture = LinkedFixture::new();
let added = fixture.observation_add(&fixture.reader, &fixture.seed_revision_id, "cross note");
let observation_id = added["observationId"].as_str().unwrap().to_owned();
let seen = observation_list_json(&fixture.seed, &fixture.seed_revision_id);
assert!(contains_observation(&seen, &observation_id));
}
#[test]
fn file_targeted_cross_worktree_fact_is_immediately_readable() {
let fixture = LinkedFixture::new();
fs::write(fixture.reader.join("README.md"), "changed in reader\n").unwrap();
let capture = fixture.capture(&fixture.reader);
let local_unit = capture["revision"]["id"].as_str().unwrap().to_owned();
let body = "z".repeat(5000);
run_shore_json(&[
"observation",
"add",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&local_unit,
"--track",
"agent:test-fixture",
"--title",
"file note",
"--file",
"README.md",
"--body",
&body,
]);
let listed = run_shore_json(&[
"observation",
"list",
"--repo",
fixture.seed.to_str().unwrap(),
"--revision",
&local_unit,
"--include-body",
]);
assert_eq!(listed["observations"][0]["body"], Value::String(body));
}
fn observation_list_json(worktree: &Path, revision_id: &str) -> Value {
run_shore_json(&[
"observation",
"list",
"--repo",
worktree.to_str().unwrap(),
"--revision",
revision_id,
])
}
fn contains_observation(list: &Value, observation_id: &str) -> bool {
list["observations"].as_array().is_some_and(|observations| {
observations
.iter()
.any(|observation| observation["id"] == Value::String(observation_id.to_owned()))
})
}
fn event_set_hash(json: &Value) -> &str {
json["eventSetHash"].as_str().expect("eventSetHash present")
}
fn run_shore_json(args: &[&str]) -> Value {
let output = shore(args.iter().copied());
assert!(
output.status.success(),
"shore {args:?} failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice(&output.stdout).expect("shore stdout is json")
}
fn add_worktree(repo: &Path, path: &Path, branch: &str) {
run_git_os(
repo,
[
OsString::from("worktree"),
OsString::from("add"),
OsString::from("-b"),
OsString::from(branch),
path.as_os_str().to_owned(),
],
);
}
fn run_git_os<I>(cwd: &Path, args: I)
where
I: IntoIterator<Item = OsString>,
{
let output = Command::new("git")
.args(args)
.current_dir(cwd)
.output()
.unwrap_or_else(|error| panic!("run git in {}: {error}", cwd.display()));
assert!(
output.status.success(),
"git failed in {}\nstdout:\n{}\nstderr:\n{}",
cwd.display(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn linked_unit_list_without_local_events_has_no_divergence_diagnostic() {
let fixture = LinkedFixture::new();
let json = fixture.unit_list_json(&fixture.reader);
assert_eq!(json["revisionCount"], 1);
assert_eq!(json["eventCount"], 2);
assert_eq!(
json["entries"][0]["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert!(
json["eventSetHash"]
.as_str()
.unwrap()
.starts_with("sha256:")
);
}
#[test]
fn linked_history_reads_full_timeline_from_linked_store() {
let fixture = LinkedFixture::new();
let body = "h".repeat(5000);
fixture.observation_add(&fixture.seed, &fixture.seed_revision_id, &body);
let json = fixture.history_json(&fixture.reader, true);
assert_eq!(json["eventCount"], 3);
let event_types: Vec<&str> = json["entries"]
.as_array()
.unwrap()
.iter()
.filter_map(|entry| entry["eventType"].as_str())
.collect();
assert!(
event_types.contains(&"work_object_proposed"),
"{event_types:?}"
);
assert!(
event_types.contains(&"review_observation_recorded"),
"{event_types:?}"
);
assert!(
json.to_string().contains(&body),
"hydrated observation body loads from the linked store"
);
}
#[test]
fn linked_unit_show_resolves_linked_only_unit() {
let fixture = LinkedFixture::new();
let body = "o".repeat(5000);
fixture.seed_full_facts(&body);
let json = fixture.unit_show_json(&fixture.reader, &fixture.seed_revision_id);
assert_eq!(
json["revision"]["id"],
Value::String(fixture.seed_revision_id.clone())
);
assert_eq!(json["summary"]["observationCount"], 1);
assert_eq!(json["summary"]["inputRequestCount"], 1);
assert_eq!(json["summary"]["assessmentCount"], 1);
assert_eq!(json["summary"]["validationCheckCount"], 1);
assert_eq!(
json["observations"][0]["body"],
Value::String(body),
"observation body hydrates from the linked store"
);
}
#[test]
fn linked_unit_show_loads_bound_snapshot_from_linked_store() {
let fixture = LinkedFixture::new();
let json = fixture.unit_show_json(&fixture.reader, &fixture.seed_revision_id);
assert_eq!(
json["revision"]["objectArtifactContentHash"],
Value::String(fixture.seed_object_artifact_content_hash.clone())
);
assert!(
json["summary"]["snapshotRowCount"].as_u64().unwrap() > 0,
"bound snapshot rows project from the linked artifact"
);
}
#[test]
fn linked_observation_list_resolves_linked_unit() {
let fixture = LinkedFixture::new();
let body = "b".repeat(5000);
fixture.seed_full_facts(&body);
let json = run_shore_json(&[
"observation",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
"--include-body",
]);
assert_eq!(
json["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert_eq!(json["observations"].as_array().unwrap().len(), 1);
assert_eq!(json["observations"][0]["body"], Value::String(body));
}
#[test]
fn linked_input_request_list_resolves_linked_unit() {
let fixture = LinkedFixture::new();
fixture.seed_full_facts("short body");
let json = run_shore_json(&[
"input-request",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
]);
assert_eq!(
json["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert_eq!(json["inputRequests"].as_array().unwrap().len(), 1);
assert_eq!(json["inputRequests"][0]["title"], "Need approval");
}
#[test]
fn linked_input_request_fetch_resolves_linked_request() {
let fixture = LinkedFixture::new();
let input_request_id = fixture.seed_full_facts("short body");
let json = run_shore_json(&[
"input-request",
"show",
&input_request_id,
"--repo",
fixture.reader.to_str().unwrap(),
"--include-body",
]);
assert_eq!(json["inputRequest"]["id"], Value::String(input_request_id));
assert_eq!(json["inputRequest"]["title"], "Need approval");
}
#[test]
fn linked_assessment_show_resolves_linked_unit() {
let fixture = LinkedFixture::new();
fixture.seed_full_facts("short body");
let json = run_shore_json(&[
"assessment",
"show",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
]);
assert_eq!(
json["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert_eq!(json["assessments"].as_array().unwrap().len(), 1);
assert_eq!(json["assessments"][0]["assessment"], "accepted");
}
#[test]
fn linked_validation_list_resolves_linked_unit() {
let fixture = LinkedFixture::new();
fixture.seed_full_facts("short body");
let json = run_shore_json(&[
"validation",
"list",
"--repo",
fixture.reader.to_str().unwrap(),
"--revision",
&fixture.seed_revision_id,
]);
assert_eq!(
json["revisionId"],
Value::String(fixture.seed_revision_id.clone())
);
assert_eq!(json["validationChecks"].as_array().unwrap().len(), 1);
assert_eq!(json["validationChecks"][0]["checkName"], "cargo test");
}
#[test]
fn object_artifact_reads_from_linked_store() {
let fixture = LinkedFixture::new();
let snapshot_id = ObjectId::new(fixture.seed_snapshot_id.clone());
let artifact = read_object_artifact(&fixture.reader, &snapshot_id)
.expect("object artifact reads from the linked store");
assert_eq!(artifact.snapshot.object_id, snapshot_id);
}
#[test]
fn export_artifact_body_reads_from_linked_store() {
let fixture = LinkedFixture::new();
let body = "x".repeat(5000);
fixture.observation_add(&fixture.seed, &fixture.seed_revision_id, &body);
let body_ref = seed_body_artifact_ref(&fixture);
let bytes = export_artifact(&fixture.reader, &body_ref)
.expect("body artifact exports from the linked store");
assert!(!bytes.is_empty());
}
#[test]
fn import_artifact_writes_through_to_linked_store() {
let fixture = LinkedFixture::new();
let body = "y".repeat(5000);
fixture.observation_add(&fixture.seed, &fixture.seed_revision_id, &body);
let body_ref = seed_body_artifact_ref(&fixture);
let artifact_relative_path = format!(
"artifacts/notes/{}.json",
body_ref
.content_hash()
.strip_prefix("sha256:")
.expect("body content hash is sha256-prefixed")
);
let bytes = fs::read(fixture.linked_store_dir().join(&artifact_relative_path)).unwrap();
import_artifact(ImportArtifactOptions::new(&fixture.reader, body_ref, bytes))
.expect("import into the linked reader succeeds");
assert!(
fixture
.linked_store_dir()
.join(&artifact_relative_path)
.is_file()
);
assert!(
!fixture
.reader
.join(".shore/data")
.join(&artifact_relative_path)
.exists()
);
}
fn seed_body_artifact_ref(fixture: &LinkedFixture) -> ArtifactRef {
let events = read_store_events(&fixture.linked_store_dir());
referenced_artifacts(&events)
.expect("derive artifact refs from seed events")
.into_iter()
.find(|artifact| artifact.kind() == ArtifactKind::Body)
.expect("seed events reference a body artifact")
}
#[test]
fn worktree_local_unit_list_is_unchanged() {
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");
run_shore_json(&["capture", "--repo", repo.path().to_str().unwrap()]);
let json = run_shore_json(&["revision", "list", "--repo", repo.path().to_str().unwrap()]);
assert_eq!(json["schema"], "pointbreak.review-revision-list");
assert_eq!(json["version"], 1);
assert_eq!(json["eventCount"], 2);
assert_eq!(json["revisionCount"], 1);
}
#[test]
fn main_worktree_of_a_clone_round_trips_a_capture_in_place() {
let main = GitRepo::new();
main.write("README.md", "base\n");
main.commit_all("base");
main.git(["checkout", "-b", "feature"]);
main.write("README.md", "changed on a branch in the main worktree\n");
let capture = run_shore_json(&["capture", "--repo", main.path().to_str().unwrap()]);
let unit_id = capture["revision"]["id"].as_str().unwrap().to_owned();
let list = run_shore_json(&["revision", "list", "--repo", main.path().to_str().unwrap()]);
assert_eq!(list["revisionCount"], 1);
assert_eq!(
list["entries"][0]["revisionId"],
Value::String(unit_id.clone())
);
let show = run_shore_json(&["revision", "show", "--repo", main.path().to_str().unwrap()]);
assert_eq!(show["revision"]["id"], Value::String(unit_id.clone()));
let history = run_shore_json(&["history", "--repo", main.path().to_str().unwrap()]);
assert!(
history["entries"]
.as_array()
.unwrap()
.iter()
.any(|entry| entry.to_string().contains(&unit_id)),
"history includes the captured unit: {}",
history["entries"]
);
let status = run_shore_json(&["store", "status", "--repo", main.path().to_str().unwrap()]);
assert_eq!(status["mode"], "local");
assert_eq!(status["inventory"]["eventCount"], 2);
}
#[test]
fn fresh_single_worktree_has_clean_own_only_reads() {
let repo = GitRepo::new();
repo.write("README.md", "base\n");
repo.commit_all("base");
repo.write("README.md", "changed locally\n");
let capture = run_shore_json(&["capture", "--repo", repo.path().to_str().unwrap()]);
let unit_id = capture["revision"]["id"].as_str().unwrap().to_owned();
let list = run_shore_json(&["revision", "list", "--repo", repo.path().to_str().unwrap()]);
assert_eq!(list["revisionCount"], 1);
assert_eq!(
list["entries"][0]["revisionId"],
Value::String(unit_id.clone())
);
let status = run_shore_json(&["store", "status", "--repo", repo.path().to_str().unwrap()]);
assert_eq!(status["mode"], "local");
assert!(
support::common_dir_store(repo.path())
.join("events")
.is_dir()
);
}