fix: preserve backend product authority boundaries
This commit is contained in:
@@ -173,7 +173,7 @@ impl BackendWorkspaceProductClient {
|
||||
}
|
||||
self.send_json(
|
||||
Method::POST,
|
||||
"/tickets/relations/search",
|
||||
"/ticket-relations/query",
|
||||
Some(&Query { ticket, kind }),
|
||||
)
|
||||
}
|
||||
@@ -568,7 +568,7 @@ impl TicketBackend for BackendWorkspaceProductClient {
|
||||
}
|
||||
self.send_json(
|
||||
Method::POST,
|
||||
"/tickets/orchestration-plans/search",
|
||||
"/ticket-orchestration-plans/query",
|
||||
Some(&Query { ticket, kind }),
|
||||
)
|
||||
.map_err(ticket_client_error)
|
||||
@@ -731,6 +731,42 @@ mod tests {
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticket_relation_query_uses_workspace_scoped_backend_route() {
|
||||
let (base_url, request, handle) = one_response_server("200 OK", "[]");
|
||||
let client = BackendWorkspaceProductClient::new(base_url, "workspace-a").unwrap();
|
||||
|
||||
let relations = client
|
||||
.query_ticket_relations(
|
||||
Some(&TicketIdOrSlug::Query("T-1".to_string())),
|
||||
Some(TicketRelationKind::Related),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(relations.is_empty());
|
||||
let request = request.recv().unwrap();
|
||||
assert!(request.starts_with("POST /api/w/workspace-a/ticket-relations/query "));
|
||||
assert!(request.contains("\"ticket\":{\"Query\":\"T-1\"}"));
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orchestration_plan_query_uses_workspace_scoped_backend_route() {
|
||||
let (base_url, request, handle) = one_response_server("200 OK", "[]");
|
||||
let client = BackendWorkspaceProductClient::new(base_url, "workspace-a").unwrap();
|
||||
|
||||
let records = TicketBackend::query_orchestration_plan_records(&client, None, None).unwrap();
|
||||
|
||||
assert!(records.is_empty());
|
||||
assert!(
|
||||
request
|
||||
.recv()
|
||||
.unwrap()
|
||||
.starts_with("POST /api/w/workspace-a/ticket-orchestration-plans/query ")
|
||||
);
|
||||
handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ticket_intake_launch_uses_backend_options_and_workspace_worker_route() {
|
||||
let (base_url, requests, handle) = response_sequence_server(vec![
|
||||
|
||||
@@ -3672,13 +3672,10 @@ impl ticket::TicketTargetAuthority for WorkspaceTicketTargetAuthority {
|
||||
}
|
||||
|
||||
fn browser_ticket_backend(api: &WorkspaceApi) -> Result<SqliteTicketBackend> {
|
||||
let config = ticket::config::TicketConfig::load_workspace(&api.config.workspace_root)
|
||||
.map_err(|error| Error::Config(format!("load Ticket workspace settings: {error}")))?;
|
||||
Ok(SqliteTicketBackend::open_verified(
|
||||
api.config.database_path.clone(),
|
||||
api.config.workspace_id.clone(),
|
||||
)?
|
||||
.with_record_language(config.ticket_record_language())
|
||||
.with_target_authority(Arc::new(WorkspaceTicketTargetAuthority {
|
||||
api: api.clone(),
|
||||
})))
|
||||
@@ -3849,14 +3846,11 @@ async fn execute_ticket_rest_operation(
|
||||
mut operation: TicketBackendOperation,
|
||||
) -> ApiResult<TicketBackendOperationResult> {
|
||||
validate_workspace_scope(api, workspace_id)?;
|
||||
let config = ticket::config::TicketConfig::load_workspace(&api.config.workspace_root)
|
||||
.map_err(|error| Error::Config(format!("load Ticket workspace settings: {error}")))?;
|
||||
let mut backend = SqliteTicketBackend::open_verified(
|
||||
api.config.database_path.clone(),
|
||||
api.config.workspace_id.clone(),
|
||||
)
|
||||
.map_err(Error::from)?
|
||||
.with_record_language(config.ticket_record_language())
|
||||
.with_target_authority(Arc::new(WorkspaceTicketTargetAuthority {
|
||||
api: api.clone(),
|
||||
}));
|
||||
@@ -16285,6 +16279,12 @@ mod tests {
|
||||
async fn ticket_browser_endpoints_mutate_typed_backend_and_return_thread() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_clean_git_workspace(dir.path());
|
||||
fs::create_dir_all(dir.path().join(".yoi")).unwrap();
|
||||
fs::write(
|
||||
dir.path().join(".yoi/workspace.toml"),
|
||||
"this is not valid workspace config",
|
||||
)
|
||||
.unwrap();
|
||||
let api = test_api(dir.path()).await;
|
||||
let ticket_ref = browser_ticket_backend(&api)
|
||||
.unwrap()
|
||||
|
||||
+55
-10
@@ -214,22 +214,15 @@ struct TargetSelection {
|
||||
workspace_id: Option<String>,
|
||||
}
|
||||
|
||||
impl TargetSelection {
|
||||
fn explicit_backend(&self) -> bool {
|
||||
self.backend_url.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_tui_target<R: CliConnectionResolver + ?Sized>(
|
||||
connection_resolver: &R,
|
||||
command: CliCommand,
|
||||
selection: &TargetSelection,
|
||||
workspace_root: &Path,
|
||||
) -> Result<Box<dyn Target>, ParseError> {
|
||||
let workspace_id = if selection.explicit_backend() && selection.workspace_id.is_none() {
|
||||
resolve_workspace_id_from_root(workspace_root)?
|
||||
} else {
|
||||
selection.workspace_id.clone()
|
||||
let workspace_id = match selection.workspace_id.clone() {
|
||||
Some(workspace_id) => Some(workspace_id),
|
||||
None => resolve_workspace_id_from_root(workspace_root)?,
|
||||
};
|
||||
resolve_connection_aware_cli_connection(
|
||||
connection_resolver,
|
||||
@@ -1755,6 +1748,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultBackendCliConnectionResolver {
|
||||
backend_url: &'static str,
|
||||
}
|
||||
|
||||
impl CliConnectionResolver for DefaultBackendCliConnectionResolver {
|
||||
fn resolve_connection(
|
||||
&self,
|
||||
_command: CliCommand,
|
||||
input: CliConnectionInput<'_>,
|
||||
) -> Result<Box<dyn Target>, ParseError> {
|
||||
let workspace_id = match input {
|
||||
CliConnectionInput::DefaultTarget { workspace_id }
|
||||
| CliConnectionInput::BackendTarget { workspace_id, .. } => workspace_id,
|
||||
CliConnectionInput::LocalTarget => return Ok(Box::new(LocalTarget::new())),
|
||||
};
|
||||
Ok(Box::new(BackendTarget::new(
|
||||
self.backend_url,
|
||||
workspace_id.map(str::to_string),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parser_uses_local_target_for_workers_without_backend_option() {
|
||||
let resolver = FixedCliConnectionResolver {
|
||||
@@ -2136,6 +2151,36 @@ backend = "shared"
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_backend_target_inherits_workspace_identity_from_workspace_root() {
|
||||
let workspace = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir_all(workspace.path().join(".yoi")).unwrap();
|
||||
std::fs::write(
|
||||
workspace.path().join(".yoi/workspace.toml"),
|
||||
"workspace_id = \"workspace-from-root\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
let resolver = DefaultBackendCliConnectionResolver {
|
||||
backend_url: "http://default-backend.example",
|
||||
};
|
||||
|
||||
let target = resolve_tui_target(
|
||||
&resolver,
|
||||
CliCommand::Ticket,
|
||||
&TargetSelection::default(),
|
||||
workspace.path(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
target.resolve().unwrap(),
|
||||
client::ResolvedTarget::Backend {
|
||||
base_url: "http://default-backend.example".to_string(),
|
||||
workspace_id: "workspace-from-root".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ticket_subcommand_uses_ticket_mode() {
|
||||
match parse_args_from(["ticket", "doctor"]).unwrap() {
|
||||
|
||||
Reference in New Issue
Block a user