Allow fresh requirement review after integration
This commit is contained in:
@@ -570,7 +570,9 @@ impl MergeRequestStore {
|
||||
}
|
||||
let mr = self.get_for_operation(&i.auth.workspace_id, &i.merge_request_id, &i.ticket_id)?;
|
||||
self.assigned(&i.auth, &i.ticket_id, &mr.repository_id)?;
|
||||
if mr.state != MergeRequestState::Open || mr.selector_from.is_none() {
|
||||
if mr.state == MergeRequestState::Closed
|
||||
|| (mr.state == MergeRequestState::Open && mr.selector_from.is_none())
|
||||
{
|
||||
return Err(MergeRequestError::Conflict(
|
||||
"Merge Request is not reviewable".into(),
|
||||
));
|
||||
@@ -587,7 +589,9 @@ impl MergeRequestStore {
|
||||
let current_mr = load_mr(&t, &i.auth.workspace_id, &i.merge_request_id)?
|
||||
.filter(|request| request.ticket_ids.iter().any(|id| id == &i.ticket_id))
|
||||
.ok_or(MergeRequestError::NotFound)?;
|
||||
if current_mr.state != MergeRequestState::Open || current_mr.selector_from.is_none() {
|
||||
if current_mr.state == MergeRequestState::Closed
|
||||
|| (current_mr.state == MergeRequestState::Open && current_mr.selector_from.is_none())
|
||||
{
|
||||
return Err(MergeRequestError::Conflict(
|
||||
"Merge Request changed before review request".into(),
|
||||
));
|
||||
@@ -659,7 +663,7 @@ impl MergeRequestStore {
|
||||
JOIN merge_requests mr
|
||||
ON mr.workspace_id=g.workspace_id AND mr.merge_request_id=g.merge_request_id
|
||||
WHERE g.capability_token=?1 AND g.merge_request_id=?2
|
||||
AND g.status='issued' AND mr.state='open'",
|
||||
AND g.status='issued' AND mr.state IN('open','merged')",
|
||||
params![capability_token, merge_request_id],
|
||||
|row| {
|
||||
Ok(ReviewSubmissionAuthorization {
|
||||
@@ -695,7 +699,7 @@ impl MergeRequestStore {
|
||||
ON mr.workspace_id=g.workspace_id AND mr.merge_request_id=g.merge_request_id
|
||||
WHERE g.capability_token=?1 AND rel.ticket_id=?2
|
||||
AND g.merge_request_id=?3
|
||||
AND g.status='issued' AND mr.state='open'",
|
||||
AND g.status='issued' AND mr.state IN('open','merged')",
|
||||
params![i.capability_token, i.ticket_id, i.merge_request_id],
|
||||
|r| {
|
||||
Ok((
|
||||
@@ -1216,18 +1220,34 @@ impl MergeRequestStore {
|
||||
))
|
||||
})?;
|
||||
let review = request
|
||||
.effective_review(&merge.approved_source_ref)
|
||||
.filter(|review| review.event_id == merge.approval_event_id)
|
||||
.thread
|
||||
.iter()
|
||||
.find_map(|event| match event {
|
||||
MergeRequestThreadEvent::Review(review)
|
||||
if review.event_id == merge.approval_event_id
|
||||
&& review.subject_ref == merge.approved_source_ref =>
|
||||
{
|
||||
Some(review)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.filter(|review| {
|
||||
!request.thread.iter().any(|event| {
|
||||
matches!(
|
||||
event,
|
||||
MergeRequestThreadEvent::ReviewRevoked(revoked)
|
||||
if revoked.review_event_id == review.event_id
|
||||
)
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
MergeRequestError::NotReady(format!(
|
||||
"Merge Request `{merge_request_id}` approval is no longer effective"
|
||||
"Merge Request `{merge_request_id}` integration approval is missing or revoked"
|
||||
))
|
||||
})?;
|
||||
if review.decision != ReviewDecision::Approve
|
||||
|| review.ticket_item_revision != input.item_revision
|
||||
{
|
||||
if review.decision != ReviewDecision::Approve {
|
||||
return Err(MergeRequestError::NotReady(format!(
|
||||
"Merge Request `{merge_request_id}` lacks approval bound to the current Ticket revision"
|
||||
"Merge Request `{merge_request_id}` lacks an approved integration result"
|
||||
)));
|
||||
}
|
||||
merged_subjects.push(MergeRequestReviewSubject {
|
||||
|
||||
@@ -859,6 +859,92 @@ fn ticket_rescope_requires_fresh_review_before_merge_and_remains_recoverable() {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticket_rescope_after_integration_accepts_fresh_requirement_attestation() {
|
||||
let (dir, store) = fixture();
|
||||
open(&store);
|
||||
let integration_approval = approve(&store, "subject", "token-integration");
|
||||
store
|
||||
.complete(CompleteMergeRequest {
|
||||
merge_request_id: "MR".into(),
|
||||
ticket_id: "T".into(),
|
||||
operation_id: "merge".into(),
|
||||
approval_event_id: integration_approval.event_id,
|
||||
current_subject_ref: "subject".into(),
|
||||
target_ref_before: "target-before".into(),
|
||||
target_ref_after: "target-after".into(),
|
||||
strategy: MergeStrategy::FastForward,
|
||||
resolution: ConflictResolution::None,
|
||||
auth: auth(),
|
||||
now: at(5),
|
||||
})
|
||||
.unwrap();
|
||||
let connection = Connection::open(dir.path().join("db")).unwrap();
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO typed_ticket_events VALUES('W','T',1,'item_edit','user','t2',NULL,NULL,NULL,NULL)",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
connection
|
||||
.execute(
|
||||
"INSERT INTO typed_ticket_event_attributes VALUES('W','T',1,'event_id','t2')",
|
||||
[],
|
||||
)
|
||||
.unwrap();
|
||||
drop(connection);
|
||||
|
||||
store
|
||||
.register_reviewer_child_session(RegisterReviewerChildSession {
|
||||
workspace_id: "W".into(),
|
||||
parent_runtime_id: "runtime".into(),
|
||||
parent_worker_id: "coder".into(),
|
||||
child_session_id: "child-post-merge".into(),
|
||||
reviewer_profile: "builtin:reviewer".into(),
|
||||
now: at(6),
|
||||
})
|
||||
.unwrap();
|
||||
store
|
||||
.request_review(RequestMergeRequestReview {
|
||||
merge_request_id: "MR".into(),
|
||||
ticket_id: "T".into(),
|
||||
ticket_item_revision: "t2".into(),
|
||||
ticket_merge_request_subjects: vec![MergeRequestReviewSubject {
|
||||
merge_request_id: "MR".into(),
|
||||
subject_ref: "subject".into(),
|
||||
}],
|
||||
subject_ref: "subject".into(),
|
||||
child_session_id: "child-post-merge".into(),
|
||||
capability_token: "token-post-merge".into(),
|
||||
auth: auth(),
|
||||
now: at(7),
|
||||
})
|
||||
.unwrap();
|
||||
let requirement_approval = store
|
||||
.submit_review(SubmitMergeRequestReview {
|
||||
merge_request_id: "MR".into(),
|
||||
ticket_id: "T".into(),
|
||||
current_subject_ref: "subject".into(),
|
||||
capability_token: "token-post-merge".into(),
|
||||
decision: ReviewDecision::Approve,
|
||||
body: "rescope requirements approved against merged result".into(),
|
||||
findings: vec![],
|
||||
now: at(8),
|
||||
})
|
||||
.unwrap();
|
||||
store
|
||||
.complete_ticket(CompleteTicket {
|
||||
ticket_id: "T".into(),
|
||||
operation_id: "ticket-complete-post-rescope".into(),
|
||||
item_revision: "t2".into(),
|
||||
merge_request_ids: vec!["MR".into()],
|
||||
requirement_approval_event_id: requirement_approval.event_id,
|
||||
auth: auth(),
|
||||
now: at(9),
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn review_request_rejects_a_snapshot_that_omits_a_linked_merge_request() {
|
||||
let (_dir, store) = fixture();
|
||||
|
||||
Reference in New Issue
Block a user