feat(slash): fondation du système de commandes slash pour la CLI custom — #162 (QA verte)
Introduit le contrat unifié des commandes slash consommable par le frontend, indépendant de la source (native, puis plugin plus tard) : - domain: modèle pur SlashCommand / SlashCommandSource / SlashCommandAvailability, métadonnées UI (nom, description, disponibilité, confirmation requise) et validation des noms/préfixes. - application: registry + list/filter par préfixe + plan d'exécution natif ; commandes natives /help et /clean (/clean = nettoyage de la vue de session courante ; pas de /reset distinct tant qu'aucune utilité produit ne le justifie). - backend + app-tauri: exposition du contrat via DTO/transport + tests DTO. Source neutre : les commandes contribuées par plugins (#165) transiteront par le même registry sans changer le contrat frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -3,13 +3,19 @@
|
||||
//! its tagged, camelCase JSON shape.
|
||||
|
||||
use app_tauri_lib::dto::{
|
||||
parse_node_id, parse_session_id, ErrorDto, HealthRequestDto, HealthResponseDto, LayoutDto,
|
||||
parse_node_id, parse_session_id, ErrorDto, ExecuteSlashCommandRequestDto,
|
||||
ExecuteSlashCommandResponseDto, HealthRequestDto, HealthResponseDto, LayoutDto,
|
||||
LayoutOperationDto, OpenTerminalRequestDto, ReattachResultDto, ResizeTerminalRequestDto,
|
||||
TerminalClosedDto, WriteTerminalRequestDto,
|
||||
SlashCommandEffectDto, SlashCommandListDto, TerminalClosedDto, WriteTerminalRequestDto,
|
||||
};
|
||||
use app_tauri_lib::events::{DomainEventDto, DOMAIN_EVENT};
|
||||
use application::{CloseTerminalOutput, LayoutOperation, LoadLayoutOutput, OpenTerminalInput};
|
||||
use domain::{Direction, LayoutNode, LayoutTree, LeafCell, NodeId, PreferredView};
|
||||
use application::{
|
||||
CloseTerminalOutput, ExecuteSlashCommandOutput, LayoutOperation, ListSlashCommandsOutput,
|
||||
LoadLayoutOutput, OpenTerminalInput, SlashCommandEffect,
|
||||
};
|
||||
use domain::{
|
||||
native_slash_commands, Direction, LayoutNode, LayoutTree, LeafCell, NodeId, PreferredView,
|
||||
};
|
||||
|
||||
use application::{AppError, HealthInput};
|
||||
use domain::events::DomainEvent;
|
||||
@ -52,6 +58,73 @@ fn health_request_deserialises_camel_case_and_defaults() {
|
||||
assert_eq!(HealthInput::from(empty).note, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_list_dto_serialises_unified_metadata() {
|
||||
let dto = SlashCommandListDto::from(ListSlashCommandsOutput {
|
||||
commands: native_slash_commands(),
|
||||
});
|
||||
let v = serde_json::to_value(&dto).unwrap();
|
||||
|
||||
assert_eq!(v["commands"][0]["name"], "/help");
|
||||
assert_eq!(v["commands"][0]["source"], "native");
|
||||
assert_eq!(v["commands"][0]["native"], "help");
|
||||
assert_eq!(
|
||||
v["commands"][0]["availability"],
|
||||
json!({ "status": "available" })
|
||||
);
|
||||
assert_eq!(v["commands"][1]["name"], "/clean");
|
||||
assert!(v["commands"][2]["requiresConfirmation"].as_bool().unwrap());
|
||||
assert_eq!(v["commands"][2]["availability"]["status"], "unavailable");
|
||||
assert!(v.get("requires_confirmation").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_execute_request_deserialises_camelcase_session() {
|
||||
let session_id = Uuid::from_u128(123).to_string();
|
||||
let dto: ExecuteSlashCommandRequestDto =
|
||||
serde_json::from_value(json!({ "name": "clean", "sessionId": session_id })).unwrap();
|
||||
|
||||
assert_eq!(dto.name, "clean");
|
||||
assert_eq!(dto.session_id.as_deref(), Some(session_id.as_str()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_execute_response_serialises_effect_shape() {
|
||||
let session_id = SessionId::from_uuid(Uuid::from_u128(456));
|
||||
let command = native_slash_commands()
|
||||
.into_iter()
|
||||
.find(|command| command.name == "/clean")
|
||||
.unwrap();
|
||||
let dto = ExecuteSlashCommandResponseDto::from(ExecuteSlashCommandOutput {
|
||||
command,
|
||||
effect: SlashCommandEffect::CleanConversation { session_id },
|
||||
});
|
||||
let v = serde_json::to_value(&dto).unwrap();
|
||||
|
||||
assert_eq!(v["command"]["name"], "/clean");
|
||||
assert_eq!(
|
||||
v["effect"],
|
||||
json!({
|
||||
"kind": "cleanConversation",
|
||||
"sessionId": session_id.to_string(),
|
||||
"clearedChunks": 0
|
||||
})
|
||||
);
|
||||
assert!(v["effect"].get("cleared_chunks").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_help_effect_carries_command_metadata() {
|
||||
let dto = SlashCommandEffectDto::Help {
|
||||
commands: native_slash_commands(),
|
||||
};
|
||||
let v = serde_json::to_value(&dto).unwrap();
|
||||
|
||||
assert_eq!(v["kind"], "help");
|
||||
assert_eq!(v["commands"][0]["name"], "/help");
|
||||
assert_eq!(v["commands"][1]["name"], "/clean");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_dto_carries_stable_code_and_message() {
|
||||
let dto = ErrorDto::from(AppError::NotFound("project".into()));
|
||||
|
||||
Reference in New Issue
Block a user