fabro/lib/apps/fabro-cli/tests/it/cmd/pr_view.rs
Bryan Helmkamp bc31b8d23a
Serve the run stream as the only run event API
Step 4 of the legacy executor deletion, third commit: the legacy event
API and every reader of it go, so that the next commits can delete the
event log, its reducer and the types beneath them.

The API:
- `GET /runs/{id}/events` pages the run stream only
  (`PaginatedRunStreamList` by `after`); the legacy `since_seq`,
  `before_seq` and `order` cursors, the `oneOf` envelope, the legacy
  `EventEnvelope`, `PaginatedEventList`, `RunEvent`, `EventSeq`,
  `AppendEventResponse` and `RunEventDetailResponse` schemas,
  `POST /runs/{id}/events`, `GET /runs/{id}/events/{seq}` and
  `GET /runs/{id}/stages/{stageId}/events` are deleted. `GET
  /runs/{id}/attach` and `GET /attach` frame `RunStreamItem`s only.
- The Rust and TypeScript clients regenerate; the removed models leave
  the TypeScript package.

The readers:
- `fabro-client` drops the legacy run event listing, tail and attach
  methods and `RunEventStream`; `list_run_stream_until` bounds a stream
  read.
- `fabro-tool`'s `fabro_run_events` lists, searches and details the run
  stream: `after` is the exclusive `stream_seq` cursor, `event_id` the
  item's id, filters match the item's name and `recorded_at`.
- `fabro-dump` writes the stream to `events.jsonl`; `fabro dump` reads
  it.
- The CLI's progress renderer keeps only what the run stream drives:
  the legacy event conversion, the sandbox and setup displays and their
  styles go. `fabro system events` prints stream items.
- The server's demo mode folds its agent fixture straight into the
  session projection and answers the attach stub with a stream item;
  the demo stage events endpoint is gone.
- The web app: every run is a Petri run. The legacy event hooks,
  renderer props, stage popover summary, run phases derivation and
  live-event payload handling are deleted or ported to `RunStreamItem`;
  toasts and board refreshes read the stream's platform records.
- Tests: the legacy API round trips and pagination tests are deleted;
  the CLI's MCP, attach and system event mocks serve stream pages; the
  CLI test helpers read stream items.

Still failing until the later commits: the CLI tests seeded through
`POST /runs/{id}/events`, the server tests over the legacy store, and
the legacy type tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 13:36:00 -04:00

223 lines
7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![allow(
clippy::absolute_paths,
reason = "This test module prefers explicit type paths over extra imports."
)]
use fabro_test::{fabro_snapshot, test_context};
use fabro_types::RunId;
use httpmock::MockServer;
use super::support::{mock_resolved_run, server_endpoint, setup_seeded_completed_dry_run};
use crate::support::unique_run_id;
#[test]
fn help() {
let context = test_context!();
let mut cmd = context.command();
cmd.args(["pr", "view", "--help"]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
View pull request details
Usage: fabro pr view [OPTIONS] <RUN_ID>
Arguments:
<RUN_ID> Run ID or prefix
Options:
--json Output as JSON [env: FABRO_JSON=]
--server <SERVER> Fabro server target: http(s) URL or absolute Unix socket path [env: FABRO_SERVER=]
--debug Enable DEBUG-level logging (default is INFO) [env: FABRO_DEBUG=]
--no-upgrade-check Disable automatic upgrade check [env: FABRO_NO_UPGRADE_CHECK=true]
--quiet Suppress non-essential output [env: FABRO_QUIET=]
--verbose Enable verbose output [env: FABRO_VERBOSE=]
-h, --help Print help
----- stderr -----
");
}
#[test]
fn pr_view_missing_pull_request_json_errors() {
let context = test_context!();
let run = setup_seeded_completed_dry_run(&context);
let mut cmd = context.command();
cmd.args(["pr", "view", &run.run_id]);
fabro_snapshot!(context.filters(), cmd, @"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No pull request found in store. Create one first with: fabro pr create [ULID]
");
}
#[test]
fn pr_view_reads_pull_request_from_store_without_pull_request_json() {
let context = test_context!();
let run = setup_seeded_completed_dry_run(&context);
let run_id: RunId = run.run_id.parse().unwrap();
let runtime = tokio::runtime::Runtime::new().unwrap();
runtime.block_on(async {
let (client, base_url) =
server_endpoint(&context.storage_dir).expect("server endpoint should exist");
client
.put(format!("{base_url}/api/v1/runs/{run_id}/pull_request"))
.json(&serde_json::json!({
"html_url": "https://github.com/fabro-sh/fabro/pull/123"
}))
.send()
.await
.unwrap()
.error_for_status()
.unwrap();
});
let mut cmd = context.command();
cmd.args(["pr", "view", &run.run_id]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
#123 Pull request
URL: https://github.com/fabro-sh/fabro/pull/123
Details: unavailable (integration_unavailable)
----- stderr -----
");
}
#[test]
fn pr_view_uses_server_pull_request_endpoint_and_renders_merged_state() {
let context = test_context!();
let server = MockServer::start();
let run_id = unique_run_id();
let resolve_mock = mock_resolved_run(&server, "nightly-build", &run_id);
let detail_mock = server.mock(|when, then| {
when.method("GET")
.path(format!("/api/v1/runs/{run_id}/pull_request"));
then.status(200)
.header("Content-Type", "application/json")
.body(
serde_json::json!({
"data": {
"link": {
"owner": "fabro-sh",
"repo": "fabro",
"number": 123,
"html_url": "https://github.com/fabro-sh/fabro/pull/123"
},
"details": {
"title": "Map the constellations",
"body": "Detailed description",
"state": "closed",
"draft": false,
"merged": true,
"merged_at": "2026-04-06T12:30:00Z",
"mergeable": false,
"additions": 10,
"deletions": 3,
"changed_files": 2,
"author": {
"login": "testuser"
},
"head_branch": "fabro/run/demo",
"base_branch": "main",
"timestamps": {
"created_at": "2026-04-05T12:00:00Z",
"updated_at": "2026-04-06T12:30:00Z"
}
}
},
"meta": {
"details_status": "available"
}
})
.to_string(),
);
});
let mut cmd = context.command();
cmd.args([
"pr",
"view",
"--server",
&server.base_url(),
"nightly-build",
]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
#123 Map the constellations
State: merged
URL: https://github.com/fabro-sh/fabro/pull/123
Branch: fabro/run/demo -> main
Author: testuser
Changes: +10 -3 (2 files)
----- stderr -----
");
resolve_mock.assert();
detail_mock.assert();
}
#[test]
fn pr_view_renders_unavailable_details_reason() {
let context = test_context!();
let server = MockServer::start();
let run_id = unique_run_id();
let resolve_mock = mock_resolved_run(&server, "nightly-build", &run_id);
let detail_mock = server.mock(|when, then| {
when.method("GET")
.path(format!("/api/v1/runs/{run_id}/pull_request"));
then.status(200)
.header("Content-Type", "application/json")
.body(
serde_json::json!({
"data": {
"link": {
"owner": "acme",
"repo": "widgets",
"number": 42,
"html_url": "https://github.com/acme/widgets/pull/42"
},
"details": null
},
"meta": {
"details_status": "unavailable",
"details_unavailable_reason": "fetch_failed"
}
})
.to_string(),
);
});
let mut cmd = context.command();
cmd.args([
"pr",
"view",
"--server",
&server.base_url(),
"nightly-build",
]);
fabro_snapshot!(context.filters(), cmd, @"
success: true
exit_code: 0
----- stdout -----
#42 Pull request
URL: https://github.com/acme/widgets/pull/42
Details: unavailable (fetch_failed)
----- stderr -----
");
resolve_mock.assert();
detail_mock.assert();
}