Files
IdeA/crates/app-tauri/tests/dto_templates.rs
Blomios 785e9935fd feat(memory): config embedders (LOT C2) + suggestion contextuelle (LOT C3) + contexte projet partagé
- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs
  (List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés,
  environnement local détecté, stratégies compilées). UI EmbedderSettings.
- LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la
  mémoire dépasse le budget de recall sans embedder configuré (event
  EmbedderSuggested, anti-spam 1×/session, « ne plus demander »).
- Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les
  agents/profils au lancement, avant la persona. UI ProjectContextPanel.

Tests : backend workspace vert (0 échec) ; frontend 306/306.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 09:24:51 +02:00

289 lines
9.3 KiB
Rust

//! L7 tests for template & sync DTO (de)serialisation contract: camelCase on
//! the wire, `TemplateDto`/`TemplateListDto` shapes, `AgentDriftDto`/
//! `AgentDriftListDto`, `SyncResultDto`, request DTO deserialisation, and
//! `parse_template_id` error behaviour. No Tauri runtime required.
use app_tauri_lib::dto::{
parse_template_id, AgentDriftDto, AgentDriftListDto, CreateAgentFromTemplateRequestDto,
CreateTemplateRequestDto, SyncResultDto, TemplateDto, TemplateListDto,
UpdateTemplateRequestDto,
};
use application::{
AgentDrift, CreateTemplateOutput, DetectAgentDriftOutput, ListTemplatesOutput,
SyncAgentWithTemplateOutput, UpdateTemplateOutput,
};
use domain::ids::{AgentId, ProfileId, TemplateId};
use domain::markdown::MarkdownDoc;
use domain::template::{AgentTemplate, TemplateVersion};
use serde_json::json;
use uuid::Uuid;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
fn make_template(template_uuid: u128, profile_uuid: u128) -> AgentTemplate {
AgentTemplate::new(
TemplateId::from_uuid(Uuid::from_u128(template_uuid)),
"My Template",
MarkdownDoc::new("# Hello".to_owned()),
ProfileId::from_uuid(Uuid::from_u128(profile_uuid)),
)
.expect("valid template")
}
// ---------------------------------------------------------------------------
// TemplateDto serialisation
// ---------------------------------------------------------------------------
#[test]
fn template_dto_serialises_camelcase() {
let tmpl = make_template(1, 2);
let dto = TemplateDto(tmpl.clone());
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["id"], tmpl.id.to_string());
assert_eq!(v["name"], "My Template");
// contentMd is the camelCase field on AgentTemplate; MarkdownDoc is transparent → plain string
assert_eq!(v["contentMd"], "# Hello");
// version is a transparent number
assert_eq!(v["version"], 1u64);
assert_eq!(
v["defaultProfileId"],
ProfileId::from_uuid(Uuid::from_u128(2)).to_string()
);
// no snake_case leak
assert!(
v.get("content_md").is_none(),
"no snake_case leak for contentMd"
);
assert!(
v.get("default_profile_id").is_none(),
"no snake_case leak for defaultProfileId"
);
}
#[test]
fn template_dto_version_is_number() {
let tmpl = make_template(3, 4);
let dto = TemplateDto(tmpl);
let v = serde_json::to_value(&dto).unwrap();
assert!(v["version"].is_number(), "version should be a JSON number");
assert_eq!(v["version"].as_u64().unwrap(), 1);
}
// ---------------------------------------------------------------------------
// TemplateListDto
// ---------------------------------------------------------------------------
#[test]
fn template_list_dto_is_transparent_array() {
let out = ListTemplatesOutput {
templates: vec![make_template(1, 2), make_template(3, 4)],
};
let dto = TemplateListDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
let arr = v.as_array().expect("transparent array");
assert_eq!(arr.len(), 2);
assert_eq!(arr[0]["name"], "My Template");
assert_eq!(arr[1]["name"], "My Template");
}
#[test]
fn template_list_dto_empty() {
let out = ListTemplatesOutput { templates: vec![] };
let dto = TemplateListDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
assert!(v.as_array().unwrap().is_empty());
}
// ---------------------------------------------------------------------------
// From<CreateTemplateOutput> / From<UpdateTemplateOutput>
// ---------------------------------------------------------------------------
#[test]
fn create_template_output_maps_to_template_dto() {
let tmpl = make_template(5, 6);
let out = CreateTemplateOutput {
template: tmpl.clone(),
};
let dto = TemplateDto::from(out);
assert_eq!(dto.0.id, tmpl.id);
}
#[test]
fn update_template_output_maps_to_template_dto() {
let tmpl = make_template(7, 8);
let bumped = tmpl.with_updated_content(MarkdownDoc::new("# Updated".to_owned()));
let out = UpdateTemplateOutput {
template: bumped.clone(),
};
let dto = TemplateDto::from(out);
assert_eq!(dto.0.version, TemplateVersion(2));
assert_eq!(dto.0.id, bumped.id);
}
// ---------------------------------------------------------------------------
// AgentDriftDto / AgentDriftListDto serialisation
// ---------------------------------------------------------------------------
#[test]
fn agent_drift_dto_serialises_camelcase() {
let agent_id = AgentId::from_uuid(Uuid::from_u128(10));
let drift = AgentDrift {
agent_id,
from: TemplateVersion(1),
to: TemplateVersion(3),
};
let dto = AgentDriftDto::from(drift);
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["agentId"], agent_id.to_string());
assert_eq!(v["from"], 1u64);
assert_eq!(v["to"], 3u64);
// no snake_case leak
assert!(
v.get("agent_id").is_none(),
"no snake_case leak for agentId"
);
}
#[test]
fn agent_drift_list_dto_is_transparent_array() {
let agent_id = AgentId::from_uuid(Uuid::from_u128(11));
let out = DetectAgentDriftOutput {
drifts: vec![AgentDrift {
agent_id,
from: TemplateVersion(2),
to: TemplateVersion(5),
}],
};
let dto = AgentDriftListDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
let arr = v.as_array().expect("transparent array");
assert_eq!(arr.len(), 1);
assert_eq!(arr[0]["from"], 2u64);
assert_eq!(arr[0]["to"], 5u64);
}
#[test]
fn agent_drift_list_dto_empty() {
let out = DetectAgentDriftOutput { drifts: vec![] };
let dto = AgentDriftListDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
assert!(v.as_array().unwrap().is_empty());
}
// ---------------------------------------------------------------------------
// SyncResultDto
// ---------------------------------------------------------------------------
#[test]
fn sync_result_dto_synced_with_version() {
let out = SyncAgentWithTemplateOutput {
synced: true,
version: Some(TemplateVersion(4)),
};
let dto = SyncResultDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["synced"], true);
assert_eq!(v["version"], 4u64);
}
#[test]
fn sync_result_dto_not_synced_version_null() {
let out = SyncAgentWithTemplateOutput {
synced: false,
version: None,
};
let dto = SyncResultDto::from(out);
let v = serde_json::to_value(&dto).unwrap();
assert_eq!(v["synced"], false);
assert!(v["version"].is_null(), "version should be null when None");
}
// ---------------------------------------------------------------------------
// Request DTO deserialisation
// ---------------------------------------------------------------------------
#[test]
fn create_template_request_deserialises_camelcase() {
let profile_id = Uuid::from_u128(20).to_string();
let raw = json!({
"name": "Backend Template",
"content": "# My context",
"defaultProfileId": profile_id
});
let dto: CreateTemplateRequestDto = serde_json::from_value(raw).unwrap();
assert_eq!(dto.name, "Backend Template");
assert_eq!(dto.content, "# My context");
assert_eq!(dto.default_profile_id, profile_id);
}
#[test]
fn update_template_request_deserialises_camelcase() {
let template_id = Uuid::from_u128(30).to_string();
let raw = json!({
"templateId": template_id,
"content": "# Updated content"
});
let dto: UpdateTemplateRequestDto = serde_json::from_value(raw).unwrap();
assert_eq!(dto.template_id, template_id);
assert_eq!(dto.content, "# Updated content");
}
#[test]
fn create_agent_from_template_request_deserialises_camelcase() {
let project_id = Uuid::from_u128(40).to_string();
let template_id = Uuid::from_u128(41).to_string();
let raw = json!({
"projectId": project_id,
"templateId": template_id,
"name": "My Agent",
"synchronized": true
});
let dto: CreateAgentFromTemplateRequestDto = serde_json::from_value(raw).unwrap();
assert_eq!(dto.project_id, project_id);
assert_eq!(dto.template_id, template_id);
assert_eq!(dto.name.as_deref(), Some("My Agent"));
assert!(dto.synchronized);
}
#[test]
fn create_agent_from_template_request_name_defaults_to_none() {
let raw = json!({
"projectId": Uuid::nil().to_string(),
"templateId": Uuid::nil().to_string(),
"synchronized": false
});
let dto: CreateAgentFromTemplateRequestDto = serde_json::from_value(raw).unwrap();
assert!(dto.name.is_none());
}
// ---------------------------------------------------------------------------
// parse_template_id
// ---------------------------------------------------------------------------
#[test]
fn parse_template_id_accepts_uuid() {
let id = Uuid::from_u128(99);
assert_eq!(
parse_template_id(&id.to_string()).unwrap(),
TemplateId::from_uuid(id)
);
}
#[test]
fn parse_template_id_rejects_garbage() {
let err = parse_template_id("INVALID").expect_err("garbage rejected");
assert_eq!(err.code, "INVALID");
}
#[test]
fn parse_template_id_rejects_empty() {
let err = parse_template_id("").expect_err("empty string rejected");
assert_eq!(err.code, "INVALID");
}