fix: align queue eligibility with target authority
This commit is contained in:
@@ -704,6 +704,15 @@ impl SqliteWorkspaceAuthority {
|
|||||||
&self,
|
&self,
|
||||||
reference: &str,
|
reference: &str,
|
||||||
request: TicketShowRequest,
|
request: TicketShowRequest,
|
||||||
|
) -> Result<TicketDetail> {
|
||||||
|
self.read_ticket_detail_with_backend(reference, request, &self.ticket_backend)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn read_ticket_detail_with_backend(
|
||||||
|
&self,
|
||||||
|
reference: &str,
|
||||||
|
request: TicketShowRequest,
|
||||||
|
backend: &SqliteTicketBackend,
|
||||||
) -> Result<TicketDetail> {
|
) -> Result<TicketDetail> {
|
||||||
let id = self
|
let id = self
|
||||||
.store
|
.store
|
||||||
@@ -713,19 +722,19 @@ impl SqliteWorkspaceAuthority {
|
|||||||
reference,
|
reference,
|
||||||
)?
|
)?
|
||||||
.ok_or_else(|| Error::Ticket(ticket::TicketError::NotFound(reference.to_string())))?;
|
.ok_or_else(|| Error::Ticket(ticket::TicketError::NotFound(reference.to_string())))?;
|
||||||
let ticket = self.ticket_backend.show(TicketIdOrSlug::Id(id))?;
|
let ticket = backend.show(TicketIdOrSlug::Id(id))?;
|
||||||
self.ticket_detail_from_ticket(ticket, request)
|
self.ticket_detail_from_ticket(ticket, request, backend)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ticket_detail_from_ticket(
|
fn ticket_detail_from_ticket(
|
||||||
&self,
|
&self,
|
||||||
ticket: ticket::Ticket,
|
ticket: ticket::Ticket,
|
||||||
request: TicketShowRequest,
|
request: TicketShowRequest,
|
||||||
|
dependency_backend: &SqliteTicketBackend,
|
||||||
) -> Result<TicketDetail> {
|
) -> Result<TicketDetail> {
|
||||||
let id = ticket.meta.id.as_str();
|
let id = ticket.meta.id.as_str();
|
||||||
let dependency_check = self
|
let dependency_check =
|
||||||
.ticket_backend
|
dependency_backend.dependency_check(TicketIdOrSlug::Id(id.to_string()))?;
|
||||||
.dependency_check(TicketIdOrSlug::Id(id.to_string()))?;
|
|
||||||
let (body, body_truncated) =
|
let (body, body_truncated) =
|
||||||
truncate_body(ticket.document.body.as_str(), DETAIL_BODY_LIMIT);
|
truncate_body(ticket.document.body.as_str(), DETAIL_BODY_LIMIT);
|
||||||
let event_limit = request
|
let event_limit = request
|
||||||
@@ -1117,6 +1126,7 @@ impl TicketAuthority for SqliteWorkspaceAuthority {
|
|||||||
event_limit: Some(TICKET_EVENT_LIMIT),
|
event_limit: Some(TICKET_EVENT_LIMIT),
|
||||||
event_cursor: None,
|
event_cursor: None,
|
||||||
},
|
},
|
||||||
|
&self.ticket_backend,
|
||||||
)?;
|
)?;
|
||||||
if ticket_matches_query(
|
if ticket_matches_query(
|
||||||
&summary,
|
&summary,
|
||||||
|
|||||||
@@ -3133,7 +3133,7 @@ async fn scoped_get_ticket(
|
|||||||
AxumPath(path): AxumPath<ScopedRecordPath>,
|
AxumPath(path): AxumPath<ScopedRecordPath>,
|
||||||
) -> ApiResult<Json<TicketDetail>> {
|
) -> ApiResult<Json<TicketDetail>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
get_ticket(State(api), AxumPath(path.id)).await
|
browser_ticket_detail(&api, &path.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_query_tickets(
|
async fn scoped_query_tickets(
|
||||||
@@ -3151,7 +3151,10 @@ async fn scoped_show_ticket(
|
|||||||
Json(query): Json<TicketShowRequest>,
|
Json(query): Json<TicketShowRequest>,
|
||||||
) -> ApiResult<Json<TicketDetail>> {
|
) -> ApiResult<Json<TicketDetail>> {
|
||||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||||
Ok(Json(api.authority.show_ticket(&path.id, query)?))
|
let backend = browser_ticket_backend(&api)?;
|
||||||
|
Ok(Json(api.authority.read_ticket_detail_with_backend(
|
||||||
|
&path.id, query, &backend,
|
||||||
|
)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
@@ -3766,7 +3769,12 @@ fn browser_ticket_backend(api: &WorkspaceApi) -> Result<SqliteTicketBackend> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn browser_ticket_detail(api: &WorkspaceApi, ticket_id: &str) -> ApiResult<Json<TicketDetail>> {
|
fn browser_ticket_detail(api: &WorkspaceApi, ticket_id: &str) -> ApiResult<Json<TicketDetail>> {
|
||||||
Ok(Json(api.authority.ticket(ticket_id)?))
|
let backend = browser_ticket_backend(api)?;
|
||||||
|
Ok(Json(api.authority.read_ticket_detail_with_backend(
|
||||||
|
ticket_id,
|
||||||
|
TicketShowRequest::default(),
|
||||||
|
&backend,
|
||||||
|
)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn scoped_edit_ticket_item(
|
async fn scoped_edit_ticket_item(
|
||||||
@@ -16521,6 +16529,60 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn browser_queue_eligibility_uses_authoritative_dependency_targets() {
|
||||||
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
init_clean_git_workspace(dir.path());
|
||||||
|
let api = test_api(dir.path()).await;
|
||||||
|
let backend = browser_ticket_backend(&api).unwrap();
|
||||||
|
let mut dependency_input = ticket::NewTicket::new("Invalid target dependency");
|
||||||
|
dependency_input.workflow_state = Some(TicketWorkflowState::Ready);
|
||||||
|
dependency_input.repository_id = Some(TEST_REPOSITORY_ID.to_string());
|
||||||
|
dependency_input.ref_selector = Some("missing-ref".to_string());
|
||||||
|
let dependency = backend.create(dependency_input).unwrap();
|
||||||
|
let mut root_input = ticket::NewTicket::new("Queue root");
|
||||||
|
root_input.workflow_state = Some(TicketWorkflowState::Ready);
|
||||||
|
root_input.repository_id = Some(TEST_REPOSITORY_ID.to_string());
|
||||||
|
root_input.ref_selector = Some("develop".to_string());
|
||||||
|
let root = backend.create(root_input).unwrap();
|
||||||
|
backend
|
||||||
|
.add_ticket_relation(
|
||||||
|
root.id.clone().into(),
|
||||||
|
ticket::NewTicketRelation {
|
||||||
|
kind: ticket::TicketRelationKind::DependsOn,
|
||||||
|
target: dependency.id.clone(),
|
||||||
|
note: None,
|
||||||
|
author: Some("test".to_string()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assign_test_orchestrator(&api, &root.id);
|
||||||
|
assign_test_orchestrator(&api, &dependency.id);
|
||||||
|
|
||||||
|
let Json(detail) = browser_ticket_detail(&api, &root.id).unwrap();
|
||||||
|
assert!(!detail.action_eligibility.can_queue);
|
||||||
|
assert!(
|
||||||
|
detail
|
||||||
|
.action_eligibility
|
||||||
|
.blockers
|
||||||
|
.iter()
|
||||||
|
.any(|blocker| blocker.contains("missing-ref"))
|
||||||
|
);
|
||||||
|
let result = scoped_queue_ticket_record(
|
||||||
|
State(api.clone()),
|
||||||
|
AxumPath((TEST_WORKSPACE_ID.to_string(), root.id.clone())),
|
||||||
|
HeaderMap::new(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
for ticket_id in [dependency.id, root.id] {
|
||||||
|
assert_eq!(
|
||||||
|
backend.show(ticket_id.into()).unwrap().meta.workflow_state,
|
||||||
|
TicketWorkflowState::Ready
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn queue_requires_assignments_for_every_ready_dependency() {
|
async fn queue_requires_assignments_for_every_ready_dependency() {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user