huskies: merge 974
This commit is contained in:
@@ -344,6 +344,8 @@ fn map_stage_move_to_event(
|
|||||||
(Stage::MergeFailure { .. }, "current") => Ok(PipelineEvent::FixupRequested),
|
(Stage::MergeFailure { .. }, "current") => Ok(PipelineEvent::FixupRequested),
|
||||||
// Story 972: send MergeFailure story back to Qa for a QA agent to re-review.
|
// Story 972: send MergeFailure story back to Qa for a QA agent to re-review.
|
||||||
(Stage::MergeFailure { .. }, "qa") => Ok(PipelineEvent::ReQueuedForQa),
|
(Stage::MergeFailure { .. }, "qa") => Ok(PipelineEvent::ReQueuedForQa),
|
||||||
|
// Story 974: reopen a Done story for a post-merge hotfix.
|
||||||
|
(Stage::Done { .. }, "current") => Ok(PipelineEvent::HotfixRequested),
|
||||||
(
|
(
|
||||||
Stage::Archived {
|
Stage::Archived {
|
||||||
reason: ArchiveReason::Blocked { .. },
|
reason: ArchiveReason::Blocked { .. },
|
||||||
@@ -809,6 +811,61 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Story 974: Done → Coding hotfix tests ────────────────────────────────
|
||||||
|
|
||||||
|
/// AC1 (story 974): move_story_to_stage to "current" from Done succeeds
|
||||||
|
/// and lands the story in Coding stage so auto-assign can pick it up.
|
||||||
|
#[test]
|
||||||
|
fn move_story_to_stage_from_done_lands_in_coding() {
|
||||||
|
crate::crdt_state::init_for_test();
|
||||||
|
crate::db::ensure_content_store();
|
||||||
|
|
||||||
|
let story_id = "99974_story_hotfix_done_to_coding";
|
||||||
|
crate::db::write_item_with_content(
|
||||||
|
story_id,
|
||||||
|
"done",
|
||||||
|
"---\nname: Hotfix Test\n---\n",
|
||||||
|
crate::db::ItemMeta::named("Hotfix Test"),
|
||||||
|
);
|
||||||
|
|
||||||
|
move_story_to_stage(story_id, "current").expect("move from Done to Coding must succeed");
|
||||||
|
|
||||||
|
let item = crate::pipeline_state::read_typed(story_id)
|
||||||
|
.expect("CRDT read must succeed")
|
||||||
|
.expect("item must exist");
|
||||||
|
assert_eq!(
|
||||||
|
item.stage.dir_name(),
|
||||||
|
"coding",
|
||||||
|
"story must be in coding after hotfix move"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// AC1 (story 974): retry_count is reset to 0 after Done→Coding so the
|
||||||
|
/// fresh coder session starts clean.
|
||||||
|
#[test]
|
||||||
|
fn move_story_from_done_to_coding_resets_retry_count() {
|
||||||
|
crate::crdt_state::init_for_test();
|
||||||
|
crate::db::ensure_content_store();
|
||||||
|
|
||||||
|
let story_id = "99975_story_hotfix_retry_count";
|
||||||
|
crate::db::write_item_with_content(
|
||||||
|
story_id,
|
||||||
|
"done",
|
||||||
|
"---\nname: Hotfix Retry Count\n---\n",
|
||||||
|
crate::db::ItemMeta::named("Hotfix Retry Count"),
|
||||||
|
);
|
||||||
|
|
||||||
|
move_story_to_stage(story_id, "current").expect("move from Done to Coding must succeed");
|
||||||
|
|
||||||
|
let retry_count = crate::crdt_state::read_item(story_id)
|
||||||
|
.expect("CRDT item must exist")
|
||||||
|
.retry_count();
|
||||||
|
assert_eq!(
|
||||||
|
retry_count, 0,
|
||||||
|
"retry_count must be 0 after hotfix move so coder starts fresh"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Bug 226: feature_branch_has_unmerged_changes returns false when no
|
/// Bug 226: feature_branch_has_unmerged_changes returns false when no
|
||||||
/// feature branch exists.
|
/// feature branch exists.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -993,4 +993,40 @@ fn move_story_merge_to_current_succeeds() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Story 974: Done → Coding (hotfix) ─────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hotfix_requested_from_done_lands_in_coding() {
|
||||||
|
let done = Stage::Done {
|
||||||
|
merged_at: chrono::Utc::now(),
|
||||||
|
merge_commit: sha("abc123"),
|
||||||
|
};
|
||||||
|
let result = transition(done, PipelineEvent::HotfixRequested).unwrap();
|
||||||
|
assert!(
|
||||||
|
matches!(result, Stage::Coding),
|
||||||
|
"Done + HotfixRequested must land in Coding; got: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hotfix_requested_rejected_from_non_done_stages() {
|
||||||
|
for stage in [
|
||||||
|
Stage::Backlog,
|
||||||
|
Stage::Coding,
|
||||||
|
Stage::Qa,
|
||||||
|
Stage::Merge {
|
||||||
|
feature_branch: fb("feature/story-1"),
|
||||||
|
commits_ahead: nz(1),
|
||||||
|
},
|
||||||
|
] {
|
||||||
|
let result = transition(stage.clone(), PipelineEvent::HotfixRequested);
|
||||||
|
assert!(
|
||||||
|
result.is_err(),
|
||||||
|
"HotfixRequested must be rejected from {:?}",
|
||||||
|
stage
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── ProjectionError Display ─────────────────────────────────────────
|
// ── ProjectionError Display ─────────────────────────────────────────
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ pub enum PipelineEvent {
|
|||||||
ReQueuedForQa,
|
ReQueuedForQa,
|
||||||
/// Story 973: user aborts an in-flight merge, sending the story back to Coding.
|
/// Story 973: user aborts an in-flight merge, sending the story back to Coding.
|
||||||
MergeAborted,
|
MergeAborted,
|
||||||
|
/// Story 974: user re-opens a Done story for a post-merge hotfix, sending it back to Coding.
|
||||||
|
HotfixRequested,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Per-node execution events ───────────────────────────────────────────────
|
// ── Per-node execution events ───────────────────────────────────────────────
|
||||||
@@ -120,6 +122,7 @@ pub fn event_label(e: &PipelineEvent) -> &'static str {
|
|||||||
PipelineEvent::FixupRequested => "FixupRequested",
|
PipelineEvent::FixupRequested => "FixupRequested",
|
||||||
PipelineEvent::ReQueuedForQa => "ReQueuedForQa",
|
PipelineEvent::ReQueuedForQa => "ReQueuedForQa",
|
||||||
PipelineEvent::MergeAborted => "MergeAborted",
|
PipelineEvent::MergeAborted => "MergeAborted",
|
||||||
|
PipelineEvent::HotfixRequested => "HotfixRequested",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,6 +319,12 @@ pub fn transition(state: Stage, event: PipelineEvent) -> Result<Stage, Transitio
|
|||||||
// ── MergeAborted: Merge → Coding (abort in-flight merge) ─────────
|
// ── MergeAborted: Merge → Coding (abort in-flight merge) ─────────
|
||||||
(Merge { .. }, MergeAborted) => Ok(Coding),
|
(Merge { .. }, MergeAborted) => Ok(Coding),
|
||||||
|
|
||||||
|
// ── HotfixRequested: Done → Coding (post-merge hotfix) ───────────
|
||||||
|
// Allows reopening a completed story so a coder can apply a hotfix.
|
||||||
|
// A fresh feature branch is forked from master when auto-assign spawns
|
||||||
|
// the coder.
|
||||||
|
(Done { .. }, HotfixRequested) => Ok(Coding),
|
||||||
|
|
||||||
// ── MergemasterAttempted: MergeFailure → MergeFailureFinal ─────
|
// ── MergemasterAttempted: MergeFailure → MergeFailureFinal ─────
|
||||||
(MergeFailure { reason, .. }, MergemasterAttempted) => Ok(MergeFailureFinal { reason }),
|
(MergeFailure { reason, .. }, MergemasterAttempted) => Ok(MergeFailureFinal { reason }),
|
||||||
(MergeFailureFinal { reason }, MergemasterAttempted) => Ok(MergeFailureFinal { reason }),
|
(MergeFailureFinal { reason }, MergemasterAttempted) => Ok(MergeFailureFinal { reason }),
|
||||||
|
|||||||
Reference in New Issue
Block a user