feat: support permissioned multi-repository ticket targets
This commit is contained in:
@@ -5918,8 +5918,8 @@ pub struct TicketDetail {
|
||||
pub item_revision: String,
|
||||
pub queued_by: Option<String>,
|
||||
pub queued_at: Option<String>,
|
||||
pub repository_key: Option<String>,
|
||||
pub ref_selector: Option<String>,
|
||||
#[cfg_attr(feature = "typescript", ts(type = "Array<TicketTarget>"))]
|
||||
pub targets: Vec<ticket::TicketTarget>,
|
||||
pub risk_flags: Vec<String>,
|
||||
pub body: String,
|
||||
pub body_truncated: bool,
|
||||
@@ -6081,8 +6081,8 @@ pub struct TicketRoleAssignmentMutationResponse {
|
||||
#[cfg_attr(feature = "typescript", ts(tag = "action", rename_all = "snake_case"))]
|
||||
pub enum BrowserTicketTargetEdit {
|
||||
Set {
|
||||
repository_key: String,
|
||||
ref_selector: Option<String>,
|
||||
#[cfg_attr(feature = "typescript", ts(type = "Array<TicketTarget>"))]
|
||||
targets: Vec<ticket::TicketTarget>,
|
||||
},
|
||||
Clear,
|
||||
}
|
||||
@@ -9269,6 +9269,64 @@ mod skill_typescript_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn browser_ticket_targets_are_one_collection_authority() {
|
||||
let edit = BrowserTicketTargetEdit::Set {
|
||||
targets: vec![
|
||||
ticket::TicketTarget {
|
||||
repository_key: "main".to_string(),
|
||||
ref_selector: Some("develop".to_string()),
|
||||
access: ticket::TicketTargetAccess::ReadWrite,
|
||||
},
|
||||
ticket::TicketTarget {
|
||||
repository_key: "docs".to_string(),
|
||||
ref_selector: None,
|
||||
access: ticket::TicketTargetAccess::ReadOnly,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(edit).unwrap(),
|
||||
serde_json::json!({
|
||||
"action": "set",
|
||||
"targets": [
|
||||
{
|
||||
"repository_key": "main",
|
||||
"ref_selector": "develop",
|
||||
"access": "read_write"
|
||||
},
|
||||
{
|
||||
"repository_key": "docs",
|
||||
"access": "read_only"
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
assert!(
|
||||
serde_json::from_value::<BrowserTicketTargetEdit>(serde_json::json!({
|
||||
"action": "set",
|
||||
"repository_key": "main",
|
||||
"ref_selector": "develop"
|
||||
}))
|
||||
.is_err(),
|
||||
"the removed singular target fields must not remain an accepted authority"
|
||||
);
|
||||
assert!(
|
||||
serde_json::from_value::<BrowserEditTicketRequest>(serde_json::json!({
|
||||
"target": {
|
||||
"action": "set",
|
||||
"targets": [{
|
||||
"repository_key": "main",
|
||||
"ref_selector": "develop",
|
||||
"access": "read_write"
|
||||
}]
|
||||
}
|
||||
}))
|
||||
.is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn websocket_operations_preserve_routes_and_directional_frame_authorities() {
|
||||
use api_macros::{TransportMetadata, WebSocketOperation};
|
||||
|
||||
@@ -430,6 +430,13 @@ fn validate_ticket_browser_operations(
|
||||
request: None,
|
||||
successes: &[("200", "TicketListResponse")],
|
||||
},
|
||||
OperationContract {
|
||||
path: "/api/w/{workspace_id}/tickets",
|
||||
method: "post",
|
||||
operation_id: "ticket_create_record",
|
||||
request: Some("NewTicket"),
|
||||
successes: &[("200", "TicketRef")],
|
||||
},
|
||||
OperationContract {
|
||||
path: "/api/w/{workspace_id}/tickets/{id}",
|
||||
method: "get",
|
||||
@@ -1837,6 +1844,45 @@ mod tests {
|
||||
const TICKET_BROWSER_TYPESCRIPT: &str =
|
||||
include_str!("../../../web/workspace/src/lib/generated/ticket-api.ts");
|
||||
|
||||
#[test]
|
||||
fn ticket_browser_generator_projects_target_collections_without_singular_authority() {
|
||||
let openapi = crate::canonical_openapi_document()
|
||||
.expect("canonical OpenAPI contract must build")
|
||||
.to_json()
|
||||
.expect("canonical OpenAPI contract must serialize");
|
||||
let output = generate_ticket_browser_typescript(&openapi).unwrap();
|
||||
assert!(output.contains(
|
||||
"export type TicketTarget = { access: TicketTargetAccess; ref_selector?: string | null; repository_key: string; };"
|
||||
));
|
||||
assert!(
|
||||
output.contains("export type TicketTargetAccess = \"read_only\" | \"read_write\";")
|
||||
);
|
||||
assert!(output.contains(
|
||||
"export type BrowserTicketTargetEdit = { action: \"set\"; targets: Array<TicketTarget>; } | { action: \"clear\"; };"
|
||||
));
|
||||
let edit = output
|
||||
.lines()
|
||||
.find(|line| line.starts_with("export type BrowserEditTicketRequest ="))
|
||||
.expect("BrowserEditTicketRequest declaration");
|
||||
assert!(edit.contains("target?: BrowserTicketTargetEdit | null;"));
|
||||
let create = output
|
||||
.lines()
|
||||
.find(|line| line.starts_with("export type NewTicket ="))
|
||||
.expect("NewTicket declaration");
|
||||
assert!(create.contains("targets?: Array<TicketTarget>;"));
|
||||
assert!(!create.contains("repository_key"));
|
||||
assert!(!create.contains("ref_selector"));
|
||||
assert!(!create.contains(" target?:"));
|
||||
let detail = output
|
||||
.lines()
|
||||
.find(|line| line.starts_with("export type TicketDetail ="))
|
||||
.expect("TicketDetail declaration");
|
||||
assert!(detail.contains("targets: Array<TicketTarget>;"));
|
||||
assert!(!detail.contains("repository_key"));
|
||||
assert!(!detail.contains("ref_selector"));
|
||||
assert!(!detail.contains(" target:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checked_in_ticket_browser_typescript_is_current() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user