mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
test: de-flake pr_list and full_http_lifecycle_cancel
Two CLI/server tests racing against peer state on the shared fabro server session, surfaced by running the default nextest profile 20 times. pr_list_missing_github_credentials_errors depended on an empty shared store; if pr_view_reads_pull_request_from_store_without_pull_request_json ran first it left a PR record behind and this test hit the credentials-required branch instead of "No pull requests found." The snapshot captured the empty path, but the test name promises the error path. Seed a PullRequestCreated event against the test's own run so the store is guaranteed non-empty and the credentials-required error fires deterministically. full_http_lifecycle_cancel asserted that the cancel response body's pending_control == "cancel", but that field is re-read from the store projection after the worker has been signaled. The worker is sitting at a human gate; on hot CI it can emit a clearing event before the handler re-reads the projection, yielding a legitimate null. Relax the assertion to accept "cancel" or null; durable convergence to failed/cancelled is still asserted below. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f037b68b0
commit
c6cafceae0
2 changed files with 67 additions and 4 deletions
|
|
@ -1,4 +1,13 @@
|
|||
#![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::run_event::PullRequestCreatedProps;
|
||||
use fabro_types::{EventBody, RunEvent, RunId};
|
||||
|
||||
use super::support::{server_endpoint, setup_completed_fast_dry_run};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
|
|
@ -26,17 +35,61 @@ fn help() {
|
|||
");
|
||||
}
|
||||
|
||||
// Seed a PR event against this test's own run so the store is guaranteed to
|
||||
// have at least one entry; `fabro pr list` then must load GitHub credentials
|
||||
// and fail, regardless of what peer tests have left in the shared store.
|
||||
#[test]
|
||||
fn pr_list_missing_github_credentials_errors() {
|
||||
let context = test_context!();
|
||||
let run = setup_completed_fast_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");
|
||||
let event = RunEvent {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
ts: chrono::Utc::now(),
|
||||
run_id,
|
||||
node_id: None,
|
||||
node_label: None,
|
||||
stage_id: None,
|
||||
parallel_group_id: None,
|
||||
parallel_branch_id: None,
|
||||
session_id: None,
|
||||
parent_session_id: None,
|
||||
tool_call_id: None,
|
||||
actor: None,
|
||||
body: EventBody::PullRequestCreated(PullRequestCreatedProps {
|
||||
pr_url: "https://github.com/fabro-sh/fabro/pull/123".to_string(),
|
||||
pr_number: 123,
|
||||
owner: "fabro-sh".to_string(),
|
||||
repo: "fabro".to_string(),
|
||||
base_branch: "main".to_string(),
|
||||
head_branch: "fabro/run/demo".to_string(),
|
||||
title: "Map the constellations".to_string(),
|
||||
draft: false,
|
||||
}),
|
||||
};
|
||||
client
|
||||
.post(format!("{base_url}/api/v1/runs/{run_id}/events"))
|
||||
.json(&event)
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
let mut cmd = context.command();
|
||||
cmd.args(["pr", "list"]);
|
||||
|
||||
fabro_snapshot!(context.filters(), cmd, @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
No pull requests found.
|
||||
----- stderr -----
|
||||
error: GitHub credentials required — run `fabro install` or set GITHUB_TOKEN
|
||||
");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,7 +249,17 @@ async fn full_http_lifecycle_cancel() {
|
|||
)
|
||||
.await;
|
||||
assert_eq!(body["status"], "running");
|
||||
assert_eq!(body["pending_control"], "cancel");
|
||||
// `pending_control` is computed from the store projection after the cancel
|
||||
// event is appended AND the worker is signaled. The worker is sitting at a
|
||||
// human gate; once notified it can emit a clearing event before this
|
||||
// handler re-reads the projection, so the response can legitimately
|
||||
// observe either the still-pending "cancel" or a null where the worker
|
||||
// already consumed it. Durable convergence is asserted below.
|
||||
let pending_control = &body["pending_control"];
|
||||
assert!(
|
||||
pending_control == "cancel" || pending_control.is_null(),
|
||||
"expected pending_control to be \"cancel\" or null, got {pending_control}"
|
||||
);
|
||||
|
||||
// Verify the durable store view converges to cancelled failure.
|
||||
let body = wait_for_run_state(&app, &run_id, "failed", "cancelled").await;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue