fix: preserve external Workdir Workers in sidebar
This commit is contained in:
@@ -599,7 +599,8 @@ pub struct SubscriptionWorkerWorkdirAttachment {
|
|||||||
/// Stable Worker-local routing alias.
|
/// Stable Worker-local routing alias.
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
/// Runtime-internal Repository id. Workspace-facing TypeScript contracts
|
/// Runtime-internal Repository id. Workspace-facing TypeScript contracts
|
||||||
/// omit this field and require `repository_key` from the Server projection.
|
/// omit this field and use `repository_key` for Repository Workdirs. Both
|
||||||
|
/// Repository fields are absent for non-Repository Workdirs.
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
#[cfg_attr(feature = "typescript", ts(skip))]
|
#[cfg_attr(feature = "typescript", ts(skip))]
|
||||||
#[cfg_attr(feature = "json-schema", schemars(skip))]
|
#[cfg_attr(feature = "json-schema", schemars(skip))]
|
||||||
@@ -622,7 +623,8 @@ impl SubscriptionWorkerWorkdirAttachment {
|
|||||||
validate_identifier("repository_id", repository_id, MAX_RESOURCE_ID_BYTES)
|
validate_identifier("repository_id", repository_id, MAX_RESOURCE_ID_BYTES)
|
||||||
}
|
}
|
||||||
(None, Some(repository_key)) => validate_repository_key(repository_key),
|
(None, Some(repository_key)) => validate_repository_key(repository_key),
|
||||||
_ => Err(SubscriptionValidationError::InvalidIdentifier {
|
(None, None) => Ok(()),
|
||||||
|
(Some(_), Some(_)) => Err(SubscriptionValidationError::InvalidIdentifier {
|
||||||
field: "repository_authority",
|
field: "repository_authority",
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -1009,6 +1011,43 @@ mod tests {
|
|||||||
assert!(workdir_json.get("repository_id").is_none());
|
assert!(workdir_json.get("repository_id").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_worker_attachment_has_no_repository_authority() {
|
||||||
|
let mut external_worker = worker("worker-1");
|
||||||
|
external_worker
|
||||||
|
.workdir_attachments
|
||||||
|
.push(SubscriptionWorkerWorkdirAttachment {
|
||||||
|
alias: "external".to_string(),
|
||||||
|
repository_id: None,
|
||||||
|
repository_key: None,
|
||||||
|
working_directory_id: SubscriptionWorkdirId::new("external-workdir-1").unwrap(),
|
||||||
|
});
|
||||||
|
|
||||||
|
external_worker.validate().unwrap();
|
||||||
|
let json = serde_json::to_value(&external_worker).unwrap();
|
||||||
|
let attachment = &json["workdir_attachments"][0];
|
||||||
|
assert!(attachment.get("repository_id").is_none());
|
||||||
|
assert!(attachment.get("repository_key").is_none());
|
||||||
|
assert_eq!(attachment["working_directory_id"], "external-workdir-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn worker_attachment_rejects_ambiguous_repository_authority() {
|
||||||
|
let attachment = SubscriptionWorkerWorkdirAttachment {
|
||||||
|
alias: "checkout".to_string(),
|
||||||
|
repository_id: Some("repository-id".to_string()),
|
||||||
|
repository_key: Some("repository-key".to_string()),
|
||||||
|
working_directory_id: SubscriptionWorkdirId::new("workdir-1").unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
attachment.validate(),
|
||||||
|
Err(SubscriptionValidationError::InvalidIdentifier {
|
||||||
|
field: "repository_authority"
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn subscribe_frame_has_stable_versioned_json_shape() {
|
fn subscribe_frame_has_stable_versioned_json_shape() {
|
||||||
let frame = SubscriptionFrame::new(SubscriptionFramePayload::Request(
|
let frame = SubscriptionFrame::new(SubscriptionFramePayload::Request(
|
||||||
|
|||||||
@@ -513,20 +513,29 @@ fn project_working_directory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn project_repository_keys(api: &WorkspaceApi, worker: &mut SubscriptionWorker) -> bool {
|
fn project_repository_keys(api: &WorkspaceApi, worker: &mut SubscriptionWorker) -> bool {
|
||||||
|
project_repository_keys_with(worker, |repository_id| {
|
||||||
|
api.store
|
||||||
|
.get_repository(&api.config.workspace_id, repository_id)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.map(|repository| repository.repository_key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn project_repository_keys_with(
|
||||||
|
worker: &mut SubscriptionWorker,
|
||||||
|
mut resolve_repository_key: impl FnMut(&str) -> Option<String>,
|
||||||
|
) -> bool {
|
||||||
for attachment in &mut worker.workdir_attachments {
|
for attachment in &mut worker.workdir_attachments {
|
||||||
let Some(repository_id) = attachment.repository_id.take() else {
|
let Some(repository_id) = attachment.repository_id.take() else {
|
||||||
if attachment.repository_key.is_none() {
|
// External Workdirs have no Repository identity. Their stable
|
||||||
return false;
|
// working_directory_id joins against the Workspace Workdir projection.
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Ok(Some(repository)) = api
|
let Some(repository_key) = resolve_repository_key(&repository_id) else {
|
||||||
.store
|
|
||||||
.get_repository(&api.config.workspace_id, &repository_id)
|
|
||||||
else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
attachment.repository_key = Some(repository.repository_key);
|
attachment.repository_key = Some(repository_key);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -593,6 +602,44 @@ mod tests {
|
|||||||
protocol::subscription::SubscriptionWorkerId::new("worker-1").unwrap()
|
protocol::subscription::SubscriptionWorkerId::new("worker-1").unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_workdir_attachment_does_not_remove_workspace_worker() {
|
||||||
|
let mut worker = SubscriptionWorker {
|
||||||
|
worker_id: worker_id(),
|
||||||
|
runtime_id: Some("runtime-1".to_string()),
|
||||||
|
resource_key: Some("W-1".to_string()),
|
||||||
|
availability: protocol::subscription::SubscriptionWorkerAvailability::Observed,
|
||||||
|
subject_revision: 1,
|
||||||
|
worker_state: None,
|
||||||
|
state: protocol::subscription::SubscriptionWorkerState::Idle,
|
||||||
|
has_running_internal_workers: false,
|
||||||
|
workspace_id: Some("workspace-1".to_string()),
|
||||||
|
display_name: Some("Companion".to_string()),
|
||||||
|
profile: Some("builtin:companion".to_string()),
|
||||||
|
workdir_attachments: vec![
|
||||||
|
protocol::subscription::SubscriptionWorkerWorkdirAttachment {
|
||||||
|
alias: "external".to_string(),
|
||||||
|
repository_id: None,
|
||||||
|
repository_key: None,
|
||||||
|
working_directory_id: protocol::subscription::SubscriptionWorkdirId::new(
|
||||||
|
"external-workdir-1",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(project_repository_keys_with(&mut worker, |_| {
|
||||||
|
panic!("External Workdir must not require Repository projection")
|
||||||
|
}));
|
||||||
|
worker.validate().unwrap();
|
||||||
|
assert_eq!(worker.workdir_attachments.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
worker.workdir_attachments[0].working_directory_id.as_str(),
|
||||||
|
"external-workdir-1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_with_events(events: mpsc::Receiver<protocol::Event>) -> SubscriptionResponse {
|
async fn run_with_events(events: mpsc::Receiver<protocol::Event>) -> SubscriptionResponse {
|
||||||
let (control_outbound, mut control_receiver) = mpsc::channel(1);
|
let (control_outbound, mut control_receiver) = mpsc::channel(1);
|
||||||
let (protocol_outbound, _protocol_receiver) = mpsc::channel(1);
|
let (protocol_outbound, _protocol_receiver) = mpsc::channel(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user