Add conversion and event-builder methods to TestPikaciJobFixture
Intent: Give TestPikaciJobFixture the ability to produce a full JobRecord (with caller-supplied or default log paths) and to emit RunLifecycleEvent::JobStarted / JobFinished events, so every test site can delegate to a single source of truth.
Affected files: crates/pika-news/src/pikaci_store.rs
Evidence
@@ -52,6 +54,47 @@ impl TestPikaciJobFixture {
+ pub fn into_job_record_with_logs(
+ pub fn into_event_job_record(self) -> JobRecord {
+ pub fn job_started_event(self, run_id: &str) -> RunLifecycleEvent {
+ pub fn job_finished_event(self, run_id: &str) -> RunLifecycleEvent {
Three new methods are added to TestPikaciJobFixture:
into_job_record_with_logs(host_log_path, guest_log_path) — consumes the fixture and builds a complete JobRecord, accepting explicit log paths so the PikaciRunStore::seed_fixture writer can pass real filesystem paths.
into_event_job_record() — convenience wrapper that uses placeholder /tmp/*.log paths, suitable for lifecycle-event tests that never read log files.
job_started_event(run_id) / job_finished_event(run_id) — produce the corresponding RunLifecycleEvent variants, boxing the record internally.
The import block gains RunLifecycleEvent under #[cfg(test)] to support the new return types.
// pikaci_store.rs — new imports
#[cfg(test)]
use pikaci::{
JobRecord, PreparedOutputsRecord, RemoteLinuxVmExecutionRecord,
RunLifecycleEvent, RunStatus,
};
Add conversion and event-builder methods to TestPikaciRunFixture
Intent: Provide TestPikaciRunFixture with into_run_record, run_started_event, and run_finished_event so that run-level record and event construction is also centralized.
Affected files: crates/pika-news/src/pikaci_store.rs
Evidence
@@ -87,6 +130,55 @@ impl TestPikaciRunFixture {
+ pub fn into_run_record(self, jobs: Vec<JobRecord>) -> RunRecord {
+ pub fn run_started_event(&self) -> RunLifecycleEvent {
+ pub fn run_finished_event(self) -> RunLifecycleEvent {
Three methods mirror the job-level pattern:
into_run_record(jobs) — consumes the fixture and fills every RunRecord field, accepting a pre-built Vec<JobRecord> for the jobs list. Fields that have no fixture counterpart (e.g. plan_path, all prepared_output_* transport fields) default to None or empty vecs.
run_started_event(&self) — borrows the fixture (note: &self, not consuming) so callers can clone and reuse the fixture for both start and finish events.
run_finished_event(self) — consumes the fixture, delegates to into_run_record(Vec::new()), and wraps the result in RunLifecycleEvent::RunFinished.
This means a test that needs both events can .clone() the fixture for the start event and then move it into the finish event.
Replace inline record construction in PikaciRunStore::seed_fixture
Intent: Eliminate the 40-line manual JobRecord and RunRecord construction inside the store's seeding logic by delegating to the new fixture methods.
Affected files: crates/pika-news/src/pikaci_store.rs
Evidence
@@ -166,56 +258,16 @@ impl PikaciRunStore {
- jobs.push(JobRecord {
+ jobs.push(job.clone().into_job_record_with_logs(
- let run = RunRecord {
+ let mut run = fixture.clone().into_run_record(jobs);
+ run.prepared_outputs_path = prepared_outputs_path
Inside PikaciRunStore::seed_fixture, the old code manually destructured every field of TestPikaciJobFixture to build a JobRecord, and every field of TestPikaciRunFixture to build a RunRecord. Both blocks are replaced:
// Before (job)
jobs.push(JobRecord {
id: job.id.clone(),
description: job.description.clone(),
// … 12 more fields …
});
// After
jobs.push(job.clone().into_job_record_with_logs(
host_log_path.display().to_string(),
guest_log_path.display().to_string(),
));
For the run record, the only field that cannot come from the fixture is prepared_outputs_path (computed from a local temp path), so it is set via mutation after construction:
let mut run = fixture.clone().into_run_record(jobs);
run.prepared_outputs_path = prepared_outputs_path
.as_ref()
.map(|(path, _)| path.display().to_string());
This drops ~40 lines of boilerplate from the production store-seeding path.
Remove duplicated fixture helpers from forge.rs tests
Intent: Delete the local fixture_job_event, fixture_run_event, and inlined RunLifecycleEvent construction in forge.rs, replacing them with the shared fixture builders.
Affected files: crates/pika-news/src/forge.rs
Evidence
@@ -1228,7 +1228,7 @@ mod tests {
- use pikaci::{JobRecord, RunLifecycleEvent, RunRecord, RunStatus};
+ use pikaci::RunStatus;
@@ -1254,71 +1254,7 @@ mod tests {
- fn fixture_job_event(
- fn fixture_run_event(
- fn event_line(event: RunLifecycleEvent) -> String {
+ fn event_line(event: pikaci::RunLifecycleEvent) -> String {
The forge.rs test module had its own fixture_job_event (15 lines) and fixture_run_event (30 lines) helpers plus the matching use imports. All are removed:
- Imports narrowed —
JobRecord, RunLifecycleEvent, and RunRecord are dropped from the use statement; only RunStatus remains because tests still reference it for struct-update expressions.
fixture_job_event deleted — 15-line function that built a JobRecord with placeholder fields.
fixture_run_event deleted — 30-line function that built a RunRecord with all transport fields set to None.
event_line updated — the helper still exists but now qualifies the type as pikaci::RunLifecycleEvent instead of relying on the removed import.
Migrate structured-pikaci success test to shared fixtures
Intent: Replace the four hand-built RunLifecycleEvent values in the structured pikaci success integration test with calls to the fixture builders.
Affected files: crates/pika-news/src/forge.rs
Evidence
@@ -1608,29 +1544,16 @@ mod tests {
+ let run_started = event_line(fixture.clone().run_started_event());
+ let job_started = event_line(
+ TestPikaciJobFixture::passed_remote_linux("job-one", "job one")
+ .job_started_event("pikaci-run-123"),
+ let run_finished = event_line(fixture.clone().run_finished_event());
The structured_pikaci_events test previously constructed all four lifecycle events inline. Now it uses the fixture variable (a TestPikaciRunFixture already in scope) and TestPikaciJobFixture::passed_remote_linux:
let run_started = event_line(fixture.clone().run_started_event());
let job_started = event_line(
TestPikaciJobFixture::passed_remote_linux("job-one", "job one")
.job_started_event("pikaci-run-123"),
);
let job_finished = event_line(
TestPikaciJobFixture::passed_remote_linux("job-one", "job one")
.job_finished_event("pikaci-run-123"),
);
let run_finished = event_line(fixture.clone().run_finished_event());
The same pattern applies to the second integration test (structured_custom_lane), which only needs run_started and run_finished and drops from 13 lines to 2.
Migrate structured-pikaci failure test to shared fixtures with struct-update syntax
Intent: Show how tests that need non-default field values (e.g. Failed status, custom message) use Rust's struct-update syntax with the shared fixture as a base.
Affected files: crates/pika-news/src/forge.rs
Evidence
@@ -1815,28 +1725,33 @@ mod tests {
+ TestPikaciJobFixture {
+ status: RunStatus::Failed,
+ exit_code: Some(1),
+ message: Some("cargo test failed in crate pika_core".to_string()),
+ ..TestPikaciJobFixture::passed_remote_linux("job-one", "job one")
+ }
+ .job_finished_event("pikaci-run-123"),
+ TestPikaciRunFixture {
+ status: RunStatus::Failed,
+ message: Some("prepared-output helper exited 1".to_string()),
+ ..TestPikaciRunFixture::passed(
The structured_pikaci_failure_log_keeps_job_and_run_messages test verifies rendering of failed events. Instead of calling the deleted fixture_job_event(RunStatus::Failed, …) helper, it now uses Rust struct-update syntax:
TestPikaciJobFixture {
status: RunStatus::Failed,
exit_code: Some(1),
message: Some("cargo test failed in crate pika_core".to_string()),
..TestPikaciJobFixture::passed_remote_linux("job-one", "job one")
}
.job_finished_event("pikaci-run-123")
This pattern keeps the deviation (failed status, exit code, message) visually prominent while inheriting all other defaults from the canonical constructor. The same approach is used for the TestPikaciRunFixture failure case, making the test both shorter and more intention-revealing.