feat(sdk,plugins): API publique d'accès fichiers/workspace + analyse structure (#124,#129)
This commit is contained in:
@ -7,6 +7,7 @@
|
||||
//! JSON convention already used in the domain (`agents.json` etc.).
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use application::{
|
||||
AgentBackgroundTaskState, AgentTicketState, AppError, AppExitWorkGuardDetail,
|
||||
@ -273,6 +274,408 @@ impl From<application::PluginRuntimePlugin> for PluginRuntimePluginDto {
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin workspace path request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginWorkspacePathDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Relative path under the project root.
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl From<PluginWorkspacePathDto> for application::PluginWorkspacePathInput {
|
||||
fn from(value: PluginWorkspacePathDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin workspace text write request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginWorkspaceWriteTextDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Relative path under the project root.
|
||||
pub path: String,
|
||||
/// UTF-8 content.
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
impl From<PluginWorkspaceWriteTextDto> for application::PluginWorkspaceWriteTextInput {
|
||||
fn from(value: PluginWorkspaceWriteTextDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
content: value.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin workspace binary write request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginWorkspaceWriteBinaryDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Relative path under the project root.
|
||||
pub path: String,
|
||||
/// Raw bytes.
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<PluginWorkspaceWriteBinaryDto> for application::PluginWorkspaceWriteBinaryInput {
|
||||
fn from(value: PluginWorkspaceWriteBinaryDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
bytes: value.bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin structured config document read request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginConfigDocumentReadDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Relative path under the project root.
|
||||
pub path: String,
|
||||
/// Optional explicit format. Omitted means inferred from extension.
|
||||
#[serde(default)]
|
||||
pub format: Option<String>,
|
||||
}
|
||||
|
||||
impl From<PluginConfigDocumentReadDto> for application::PluginConfigDocumentReadInput {
|
||||
fn from(value: PluginConfigDocumentReadDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
format: value.format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin structured config document update request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginConfigDocumentUpdateDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Relative path under the project root.
|
||||
pub path: String,
|
||||
/// Optional explicit format. Omitted means inferred from extension.
|
||||
#[serde(default)]
|
||||
pub format: Option<String>,
|
||||
/// Update mode: `mergePatch` (default) or `replace`.
|
||||
#[serde(default)]
|
||||
pub mode: Option<String>,
|
||||
/// JSON replacement or merge patch.
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
impl From<PluginConfigDocumentUpdateDto> for application::PluginConfigDocumentUpdateInput {
|
||||
fn from(value: PluginConfigDocumentUpdateDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
format: value.format,
|
||||
mode: value.mode,
|
||||
value: value.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin project structure query request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginProjectStructureQueryDto {
|
||||
/// Project id.
|
||||
pub project_id: String,
|
||||
/// Optional relative root path.
|
||||
#[serde(default)]
|
||||
pub path: Option<String>,
|
||||
/// Optional traversal depth.
|
||||
#[serde(default)]
|
||||
pub max_depth: Option<u8>,
|
||||
/// Optional entry cap.
|
||||
#[serde(default)]
|
||||
pub max_entries: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<PluginProjectStructureQueryDto> for application::QueryProjectStructureInput {
|
||||
fn from(value: PluginProjectStructureQueryDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
path: value.path,
|
||||
max_depth: value.max_depth,
|
||||
max_entries: value.max_entries,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin command-task launch request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginRunCommandDto {
|
||||
/// Owning project id.
|
||||
pub project_id: String,
|
||||
/// Agent id used for Work correlation and completion wake delivery.
|
||||
pub owner_agent_id: String,
|
||||
/// Human-facing task label.
|
||||
pub label: String,
|
||||
/// Executable to run.
|
||||
pub command: String,
|
||||
/// Arguments passed without shell parsing.
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
/// Relative working directory under project root. Empty/omitted means root.
|
||||
#[serde(default)]
|
||||
pub cwd: Option<String>,
|
||||
/// Extra environment variables.
|
||||
#[serde(default)]
|
||||
pub env: Vec<(String, String)>,
|
||||
/// When true, completion is only recorded; otherwise the owner is woken.
|
||||
#[serde(default)]
|
||||
pub record_only: bool,
|
||||
/// Optional absolute deadline, epoch milliseconds.
|
||||
#[serde(default)]
|
||||
pub deadline_ms: Option<u64>,
|
||||
}
|
||||
|
||||
impl From<PluginRunCommandDto> for application::PluginRunCommandInput {
|
||||
fn from(value: PluginRunCommandDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
owner_agent_id: value.owner_agent_id,
|
||||
label: value.label,
|
||||
command: value.command,
|
||||
args: value.args,
|
||||
cwd: value.cwd,
|
||||
env: value.env,
|
||||
record_only: value.record_only,
|
||||
deadline_ms: value.deadline_ms,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin task status request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginTaskStatusDto {
|
||||
/// Task id to read.
|
||||
pub task_id: String,
|
||||
}
|
||||
|
||||
impl From<PluginTaskStatusDto> for application::PluginTaskStatusInput {
|
||||
fn from(value: PluginTaskStatusDto) -> Self {
|
||||
Self {
|
||||
task_id: value.task_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin command-task status/output DTO.
|
||||
pub type PluginTaskDto = BackgroundTaskDto;
|
||||
|
||||
/// Public plugin external-toolchain diagnostic request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginToolchainDiagnosticRequestDto {
|
||||
/// Owning project id.
|
||||
pub project_id: String,
|
||||
/// Relative working directory under project root.
|
||||
#[serde(default)]
|
||||
pub cwd: Option<String>,
|
||||
/// Executable probes to run.
|
||||
#[serde(default)]
|
||||
pub tools: Vec<PluginToolRequirementDto>,
|
||||
/// Environment variable prerequisites.
|
||||
#[serde(default)]
|
||||
pub env: Vec<PluginEnvRequirementDto>,
|
||||
/// Workspace file prerequisites.
|
||||
#[serde(default)]
|
||||
pub files: Vec<PluginFileRequirementDto>,
|
||||
}
|
||||
|
||||
impl From<PluginToolchainDiagnosticRequestDto> for application::PluginToolchainDiagnosticInput {
|
||||
fn from(value: PluginToolchainDiagnosticRequestDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
cwd: value.cwd,
|
||||
tools: value.tools.into_iter().map(Into::into).collect(),
|
||||
env: value.env.into_iter().map(Into::into).collect(),
|
||||
files: value.files.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin executable probe DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginToolRequirementDto {
|
||||
/// Stable requirement id.
|
||||
pub id: String,
|
||||
/// Executable name or path.
|
||||
pub executable: String,
|
||||
/// Version/diagnostic arguments.
|
||||
#[serde(default)]
|
||||
pub version_args: Vec<String>,
|
||||
/// Whether this probe is required.
|
||||
#[serde(default)]
|
||||
pub required: bool,
|
||||
/// Extra environment variables for the probe.
|
||||
#[serde(default)]
|
||||
pub env: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl From<PluginToolRequirementDto> for application::PluginToolRequirement {
|
||||
fn from(value: PluginToolRequirementDto) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
executable: value.executable,
|
||||
version_args: value.version_args,
|
||||
required: value.required,
|
||||
env: value.env,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin environment prerequisite DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginEnvRequirementDto {
|
||||
/// Environment variable name.
|
||||
pub name: String,
|
||||
/// Whether this variable is required.
|
||||
#[serde(default)]
|
||||
pub required: bool,
|
||||
/// Optional exact expected value.
|
||||
#[serde(default)]
|
||||
pub equals: Option<String>,
|
||||
}
|
||||
|
||||
impl From<PluginEnvRequirementDto> for application::PluginEnvRequirement {
|
||||
fn from(value: PluginEnvRequirementDto) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
required: value.required,
|
||||
equals: value.equals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin workspace file prerequisite DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginFileRequirementDto {
|
||||
/// Relative path under project root.
|
||||
pub path: String,
|
||||
/// Whether this path is required.
|
||||
#[serde(default)]
|
||||
pub required: bool,
|
||||
/// Expected kind: `file`, `directory`, or `any`.
|
||||
#[serde(default)]
|
||||
pub kind: Option<String>,
|
||||
}
|
||||
|
||||
impl From<PluginFileRequirementDto> for application::PluginFileRequirement {
|
||||
fn from(value: PluginFileRequirementDto) -> Self {
|
||||
Self {
|
||||
path: value.path,
|
||||
required: value.required,
|
||||
kind: value.kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin external-toolchain diagnostic output DTO.
|
||||
pub type PluginToolchainDiagnosticDto = application::PluginToolchainDiagnostic;
|
||||
|
||||
/// Public plugin event subscription request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginEventSubscribeDto {
|
||||
/// Project id to observe.
|
||||
pub project_id: String,
|
||||
/// Public event types to retain. Empty means all supported types.
|
||||
#[serde(default)]
|
||||
pub event_types: Vec<String>,
|
||||
/// Per-subscription retained event capacity.
|
||||
#[serde(default)]
|
||||
pub capacity: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<PluginEventSubscribeDto> for application::PluginEventSubscribeInput {
|
||||
fn from(value: PluginEventSubscribeDto) -> Self {
|
||||
Self {
|
||||
project_id: value.project_id,
|
||||
event_types: value.event_types,
|
||||
capacity: value.capacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin event poll request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginEventPollDto {
|
||||
/// Subscription id returned by subscribe.
|
||||
pub subscription_id: String,
|
||||
/// Maximum number of events to drain.
|
||||
#[serde(default)]
|
||||
pub max_events: Option<usize>,
|
||||
}
|
||||
|
||||
impl From<PluginEventPollDto> for application::PluginEventPollInput {
|
||||
fn from(value: PluginEventPollDto) -> Self {
|
||||
Self {
|
||||
subscription_id: value.subscription_id,
|
||||
max_events: value.max_events,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin event unsubscribe request DTO.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PluginEventUnsubscribeDto {
|
||||
/// Subscription id returned by subscribe.
|
||||
pub subscription_id: String,
|
||||
}
|
||||
|
||||
impl From<PluginEventUnsubscribeDto> for application::PluginEventUnsubscribeInput {
|
||||
fn from(value: PluginEventUnsubscribeDto) -> Self {
|
||||
Self {
|
||||
subscription_id: value.subscription_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Public plugin event subscription output DTO.
|
||||
pub type PluginEventSubscriptionDto = application::PluginEventSubscription;
|
||||
/// Public plugin event poll output DTO.
|
||||
pub type PluginEventBatchDto = application::PluginEventBatch;
|
||||
|
||||
/// Plugin workspace text file DTO.
|
||||
pub type PluginWorkspaceTextFileDto = application::PluginWorkspaceTextFile;
|
||||
/// Plugin workspace binary file DTO.
|
||||
pub type PluginWorkspaceBinaryFileDto = application::PluginWorkspaceBinaryFile;
|
||||
/// Plugin workspace directory listing DTO.
|
||||
pub type PluginWorkspaceDirectoryListingDto = application::PluginWorkspaceDirectoryListing;
|
||||
/// Plugin workspace stat DTO.
|
||||
pub type PluginWorkspaceStatDto = application::PluginWorkspaceStat;
|
||||
/// Plugin structured config document DTO.
|
||||
pub type PluginConfigDocumentDto = application::PluginConfigDocument;
|
||||
/// Plugin structured config document write result DTO.
|
||||
pub type PluginConfigDocumentWriteResultDto = application::PluginConfigDocumentWriteResult;
|
||||
/// Plugin project structure result DTO.
|
||||
pub type PluginProjectStructureDto = application::ProjectStructureQuery;
|
||||
|
||||
/// Request DTO for the `health` command.
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -4352,6 +4755,356 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_workspace_requests_use_stable_camel_case_contract() {
|
||||
let path = PluginWorkspacePathDto {
|
||||
project_id: Uuid::from_u128(124).to_string(),
|
||||
path: "src/main.rs".to_owned(),
|
||||
};
|
||||
let text = PluginWorkspaceWriteTextDto {
|
||||
project_id: path.project_id.clone(),
|
||||
path: path.path.clone(),
|
||||
content: "fn main() {}\n".to_owned(),
|
||||
};
|
||||
let binary = PluginWorkspaceWriteBinaryDto {
|
||||
project_id: path.project_id.clone(),
|
||||
path: "assets/icon.bin".to_owned(),
|
||||
bytes: vec![1, 2, 3],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&path).unwrap(),
|
||||
json!({
|
||||
"projectId": path.project_id,
|
||||
"path": "src/main.rs"
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&text).unwrap(),
|
||||
json!({
|
||||
"projectId": text.project_id,
|
||||
"path": "src/main.rs",
|
||||
"content": "fn main() {}\n"
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&binary).unwrap(),
|
||||
json!({
|
||||
"projectId": binary.project_id,
|
||||
"path": "assets/icon.bin",
|
||||
"bytes": [1, 2, 3]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_project_structure_query_maps_to_application_input() {
|
||||
let dto = PluginProjectStructureQueryDto {
|
||||
project_id: Uuid::from_u128(129).to_string(),
|
||||
path: Some("crates".to_owned()),
|
||||
max_depth: Some(4),
|
||||
max_entries: Some(250),
|
||||
};
|
||||
|
||||
let json = serde_json::to_value(&dto).unwrap();
|
||||
assert_eq!(
|
||||
json,
|
||||
json!({
|
||||
"projectId": dto.project_id,
|
||||
"path": "crates",
|
||||
"maxDepth": 4,
|
||||
"maxEntries": 250
|
||||
})
|
||||
);
|
||||
|
||||
let input: application::QueryProjectStructureInput = dto.into();
|
||||
assert_eq!(input.path.as_deref(), Some("crates"));
|
||||
assert_eq!(input.max_depth, Some(4));
|
||||
assert_eq!(input.max_entries, Some(250));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_config_document_requests_use_stable_camel_case_contract() {
|
||||
let project_id = Uuid::from_u128(130).to_string();
|
||||
let read = PluginConfigDocumentReadDto {
|
||||
project_id: project_id.clone(),
|
||||
path: "config/settings.json".to_owned(),
|
||||
format: Some("json".to_owned()),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&read).unwrap(),
|
||||
json!({
|
||||
"projectId": project_id,
|
||||
"path": "config/settings.json",
|
||||
"format": "json"
|
||||
})
|
||||
);
|
||||
let input: application::PluginConfigDocumentReadInput = read.into();
|
||||
assert_eq!(input.path, "config/settings.json");
|
||||
assert_eq!(input.format.as_deref(), Some("json"));
|
||||
|
||||
let update = PluginConfigDocumentUpdateDto {
|
||||
project_id: Uuid::from_u128(130).to_string(),
|
||||
path: "config/settings.json".to_owned(),
|
||||
format: Some("json".to_owned()),
|
||||
mode: Some("mergePatch".to_owned()),
|
||||
value: json!({"enabled": true, "removeMe": null}),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&update).unwrap(),
|
||||
json!({
|
||||
"projectId": Uuid::from_u128(130).to_string(),
|
||||
"path": "config/settings.json",
|
||||
"format": "json",
|
||||
"mode": "mergePatch",
|
||||
"value": {
|
||||
"enabled": true,
|
||||
"removeMe": null
|
||||
}
|
||||
})
|
||||
);
|
||||
let input: application::PluginConfigDocumentUpdateInput = update.into();
|
||||
assert_eq!(input.mode.as_deref(), Some("mergePatch"));
|
||||
assert_eq!(input.value["enabled"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_workspace_outputs_use_stable_camel_case_contract() {
|
||||
let listing = PluginWorkspaceDirectoryListingDto {
|
||||
path: "src".to_owned(),
|
||||
entries: vec![application::PluginWorkspaceDirEntry {
|
||||
name: "main.rs".to_owned(),
|
||||
path: "src/main.rs".to_owned(),
|
||||
is_dir: false,
|
||||
}],
|
||||
};
|
||||
let stat = PluginWorkspaceStatDto {
|
||||
path: "src/main.rs".to_owned(),
|
||||
exists: true,
|
||||
is_file: true,
|
||||
is_dir: false,
|
||||
len: Some(13),
|
||||
};
|
||||
let structure = PluginProjectStructureDto {
|
||||
project_id: Uuid::from_u128(129).to_string(),
|
||||
root_path: String::new(),
|
||||
entries: vec![application::ProjectStructureEntry {
|
||||
path: "Cargo.toml".to_owned(),
|
||||
name: "Cargo.toml".to_owned(),
|
||||
kind: "file".to_owned(),
|
||||
}],
|
||||
conventions: vec![application::ProjectConvention {
|
||||
id: "rust-cargo".to_owned(),
|
||||
marker_path: "Cargo.toml".to_owned(),
|
||||
}],
|
||||
modules: vec![application::ProjectModule {
|
||||
path: String::new(),
|
||||
marker_path: "Cargo.toml".to_owned(),
|
||||
convention_id: "rust-cargo".to_owned(),
|
||||
}],
|
||||
truncated: false,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&listing).unwrap(),
|
||||
json!({
|
||||
"path": "src",
|
||||
"entries": [{
|
||||
"name": "main.rs",
|
||||
"path": "src/main.rs",
|
||||
"isDir": false
|
||||
}]
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&stat).unwrap(),
|
||||
json!({
|
||||
"path": "src/main.rs",
|
||||
"exists": true,
|
||||
"isFile": true,
|
||||
"isDir": false,
|
||||
"len": 13
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&structure).unwrap(),
|
||||
json!({
|
||||
"projectId": structure.project_id,
|
||||
"rootPath": "",
|
||||
"entries": [{
|
||||
"path": "Cargo.toml",
|
||||
"name": "Cargo.toml",
|
||||
"kind": "file"
|
||||
}],
|
||||
"conventions": [{
|
||||
"id": "rust-cargo",
|
||||
"markerPath": "Cargo.toml"
|
||||
}],
|
||||
"modules": [{
|
||||
"path": "",
|
||||
"markerPath": "Cargo.toml",
|
||||
"conventionId": "rust-cargo"
|
||||
}],
|
||||
"truncated": false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_command_task_requests_use_stable_camel_case_contract() {
|
||||
let project_id = Uuid::from_u128(125).to_string();
|
||||
let owner_agent_id = Uuid::from_u128(126).to_string();
|
||||
let run = PluginRunCommandDto {
|
||||
project_id: project_id.clone(),
|
||||
owner_agent_id: owner_agent_id.clone(),
|
||||
label: "cargo test".to_owned(),
|
||||
command: "cargo".to_owned(),
|
||||
args: vec!["test".to_owned(), "-p".to_owned(), "application".to_owned()],
|
||||
cwd: Some("crates/application".to_owned()),
|
||||
env: vec![("RUST_LOG".to_owned(), "debug".to_owned())],
|
||||
record_only: true,
|
||||
deadline_ms: Some(1_800_000_000_000),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&run).unwrap(),
|
||||
json!({
|
||||
"projectId": project_id,
|
||||
"ownerAgentId": owner_agent_id,
|
||||
"label": "cargo test",
|
||||
"command": "cargo",
|
||||
"args": ["test", "-p", "application"],
|
||||
"cwd": "crates/application",
|
||||
"env": [["RUST_LOG", "debug"]],
|
||||
"recordOnly": true,
|
||||
"deadlineMs": 1_800_000_000_000u64
|
||||
})
|
||||
);
|
||||
|
||||
let input: application::PluginRunCommandInput = run.into();
|
||||
assert_eq!(input.cwd.as_deref(), Some("crates/application"));
|
||||
assert_eq!(input.env, vec![("RUST_LOG".to_owned(), "debug".to_owned())]);
|
||||
assert!(input.record_only);
|
||||
|
||||
let status = PluginTaskStatusDto {
|
||||
task_id: Uuid::from_u128(127).to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&status).unwrap(),
|
||||
json!({ "taskId": status.task_id })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_toolchain_diagnostic_request_maps_to_application_input() {
|
||||
let project_id = Uuid::from_u128(126).to_string();
|
||||
let request = PluginToolchainDiagnosticRequestDto {
|
||||
project_id: project_id.clone(),
|
||||
cwd: Some("crates/backend".to_owned()),
|
||||
tools: vec![PluginToolRequirementDto {
|
||||
id: "rust".to_owned(),
|
||||
executable: "cargo".to_owned(),
|
||||
version_args: vec!["--version".to_owned()],
|
||||
required: true,
|
||||
env: vec![("CARGO_TERM_COLOR".to_owned(), "never".to_owned())],
|
||||
}],
|
||||
env: vec![PluginEnvRequirementDto {
|
||||
name: "RUSTUP_HOME".to_owned(),
|
||||
required: false,
|
||||
equals: None,
|
||||
}],
|
||||
files: vec![PluginFileRequirementDto {
|
||||
path: "Cargo.toml".to_owned(),
|
||||
required: true,
|
||||
kind: Some("file".to_owned()),
|
||||
}],
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&request).unwrap(),
|
||||
json!({
|
||||
"projectId": project_id,
|
||||
"cwd": "crates/backend",
|
||||
"tools": [{
|
||||
"id": "rust",
|
||||
"executable": "cargo",
|
||||
"versionArgs": ["--version"],
|
||||
"required": true,
|
||||
"env": [["CARGO_TERM_COLOR", "never"]]
|
||||
}],
|
||||
"env": [{
|
||||
"name": "RUSTUP_HOME",
|
||||
"required": false,
|
||||
"equals": null
|
||||
}],
|
||||
"files": [{
|
||||
"path": "Cargo.toml",
|
||||
"required": true,
|
||||
"kind": "file"
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
let input: application::PluginToolchainDiagnosticInput = request.into();
|
||||
assert_eq!(input.cwd.as_deref(), Some("crates/backend"));
|
||||
assert_eq!(input.tools[0].id, "rust");
|
||||
assert_eq!(input.tools[0].env[0].0, "CARGO_TERM_COLOR");
|
||||
assert_eq!(input.env[0].name, "RUSTUP_HOME");
|
||||
assert_eq!(input.files[0].kind.as_deref(), Some("file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dto_plugins_event_subscription_requests_use_stable_camel_case_contract() {
|
||||
let project_id = Uuid::from_u128(127).to_string();
|
||||
let subscribe = PluginEventSubscribeDto {
|
||||
project_id: project_id.clone(),
|
||||
event_types: vec![
|
||||
"workspaceFileChanged".to_owned(),
|
||||
"backgroundTaskChanged".to_owned(),
|
||||
],
|
||||
capacity: Some(250),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&subscribe).unwrap(),
|
||||
json!({
|
||||
"projectId": project_id,
|
||||
"eventTypes": ["workspaceFileChanged", "backgroundTaskChanged"],
|
||||
"capacity": 250
|
||||
})
|
||||
);
|
||||
let input: application::PluginEventSubscribeInput = subscribe.into();
|
||||
assert_eq!(
|
||||
input.event_types,
|
||||
vec![
|
||||
"workspaceFileChanged".to_owned(),
|
||||
"backgroundTaskChanged".to_owned()
|
||||
]
|
||||
);
|
||||
assert_eq!(input.capacity, Some(250));
|
||||
|
||||
let poll = PluginEventPollDto {
|
||||
subscription_id: Uuid::from_u128(128).to_string(),
|
||||
max_events: Some(50),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&poll).unwrap(),
|
||||
json!({
|
||||
"subscriptionId": poll.subscription_id,
|
||||
"maxEvents": 50
|
||||
})
|
||||
);
|
||||
let input: application::PluginEventPollInput = poll.into();
|
||||
assert_eq!(input.max_events, Some(50));
|
||||
|
||||
let unsubscribe = PluginEventUnsubscribeDto {
|
||||
subscription_id: Uuid::from_u128(129).to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
serde_json::to_value(&unsubscribe).unwrap(),
|
||||
json!({ "subscriptionId": unsubscribe.subscription_id })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_task_dto_exposes_rendezvous_context_only_for_headless_rendezvous() {
|
||||
let project_id = ProjectId::from_uuid(Uuid::from_u128(1));
|
||||
|
||||
Reference in New Issue
Block a user