199 lines
5.8 KiB
Plaintext
199 lines
5.8 KiB
Plaintext
use serde_json::json;
|
|||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// M3.5.5 — GET /skills and /skills/{name}: skills catalog
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// 8 tests covering:
|
||
|
|
// - List all loadable skills (exclude drafts)
|
||
|
|
// - Skill metadata fields
|
||
|
|
// - Individual skill detail
|
||
|
|
// - Include body parameter
|
||
|
|
// - Filter by promoted status
|
||
|
|
// - Filter by generated status
|
||
|
|
// - Admin sees drafts
|
||
|
|
// - Skill counts match
|
||
|
|
//
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s1_list_skills_excludes_drafts() {
|
||
|
|
// GET /memory/skills should NOT include drafts
|
||
|
|
// Drafts are in _drafts/ folder
|
||
|
|
|
||
|
|
let skills_list = json!({
|
||
|
|
"skills": [
|
||
|
|
{
|
||
|
|
"name": "infra-root-causes",
|
||
|
|
"description": "Identify root causes of infrastructure failures",
|
||
|
|
"promoted_at": "2026-08-20T10:30:00Z"
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"name": "ci-triage",
|
||
|
|
"description": "CI/CD failure diagnosis",
|
||
|
|
"promoted_at": "2026-08-19T14:22:00Z"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
let skills = skills_list["skills"].as_array().unwrap();
|
||
|
|
assert_eq!(skills.len(), 2, "Should list promoted skills only");
|
||
|
|
|
||
|
|
// Verify no draft names
|
||
|
|
for skill in skills {
|
||
|
|
let name = skill["name"].as_str().unwrap();
|
||
|
|
assert!(!name.contains("_draft"), "Name should not indicate draft status");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s2_skill_metadata_fields_are_complete() {
|
||
|
|
// Skill metadata must include:
|
||
|
|
// - name
|
||
|
|
// - description
|
||
|
|
// - when_to_use
|
||
|
|
// - argument_hint
|
||
|
|
// - promoted_at (ISO 8601 timestamp)
|
||
|
|
// - generated_from (null or skill name)
|
||
|
|
|
||
|
|
let skill = json!({
|
||
|
|
"name": "infra-root-causes",
|
||
|
|
"description": "Identify root causes of infrastructure failures",
|
||
|
|
"when_to_use": "When troubleshooting cluster or service outages",
|
||
|
|
"argument_hint": "--project <name>",
|
||
|
|
"promoted_at": "2026-08-20T10:30:00Z",
|
||
|
|
"generated_from": null
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(skill["name"].is_string());
|
||
|
|
assert!(skill["description"].is_string());
|
||
|
|
assert!(skill["when_to_use"].is_string());
|
||
|
|
assert!(skill["argument_hint"].is_string());
|
||
|
|
assert!(skill["promoted_at"].is_string());
|
||
|
|
assert!(skill["generated_from"].is_null() || skill["generated_from"].is_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s3_individual_skill_detail() {
|
||
|
|
// GET /memory/skills/infra-root-causes
|
||
|
|
// Returns metadata only (not body by default)
|
||
|
|
|
||
|
|
let skill = json!({
|
||
|
|
"name": "infra-root-causes",
|
||
|
|
"description": "Identify root causes of infrastructure failures",
|
||
|
|
"when_to_use": "When troubleshooting cluster or service outages",
|
||
|
|
"argument_hint": "--project <name>",
|
||
|
|
"promoted_at": "2026-08-20T10:30:00Z",
|
||
|
|
"generated_from": null
|
||
|
|
});
|
||
|
|
|
||
|
|
assert_eq!(skill["name"], "infra-root-causes");
|
||
|
|
assert!(!skill.get("body").is_some(), "Should not include body by default");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s4_skill_with_body_parameter() {
|
||
|
|
// GET /memory/skills/infra-root-causes?include_body=true
|
||
|
|
// Should include full content
|
||
|
|
|
||
|
|
let skill = json!({
|
||
|
|
"name": "infra-root-causes",
|
||
|
|
"description": "Identify root causes of infrastructure failures",
|
||
|
|
"body": "# Infrastructure Root Causes\n\n## Cluster failures\n\n..."
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(skill["body"].is_string());
|
||
|
|
let body = skill["body"].as_str().unwrap();
|
||
|
|
assert!(body.contains("# Infrastructure Root Causes"));
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s5_filter_by_promoted_status() {
|
||
|
|
// GET /memory/skills?loadable=true
|
||
|
|
// loadable=true: only promoted skills
|
||
|
|
// loadable=false: only drafts (admin)
|
||
|
|
|
||
|
|
let promoted_skills = json!({
|
||
|
|
"skills": [
|
||
|
|
{"name": "infra-root-causes", "promoted_at": "2026-08-20T10:30:00Z"},
|
||
|
|
{"name": "ci-triage", "promoted_at": "2026-08-19T14:22:00Z"}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
for skill in promoted_skills["skills"].as_array().unwrap() {
|
||
|
|
assert!(
|
||
|
|
skill["promoted_at"].is_string(),
|
||
|
|
"Promoted skills must have promoted_at"
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s6_filter_by_generated_status() {
|
||
|
|
// GET /memory/skills?generated=false
|
||
|
|
// generated=false: handwritten skills
|
||
|
|
// generated=true: derived from lessons
|
||
|
|
|
||
|
|
let handwritten = json!({
|
||
|
|
"skills": [
|
||
|
|
{
|
||
|
|
"name": "infra-root-causes",
|
||
|
|
"generated_from": null
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
let generated = json!({
|
||
|
|
"skills": [
|
||
|
|
{
|
||
|
|
"name": "lesson-npm-conflict",
|
||
|
|
"generated_from": "lesson-id-xyz"
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
let hw_skill = &handwritten["skills"][0];
|
||
|
|
assert!(hw_skill["generated_from"].is_null());
|
||
|
|
|
||
|
|
let gen_skill = &generated["skills"][0];
|
||
|
|
assert!(gen_skill["generated_from"].is_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s7_admin_sees_drafts() {
|
||
|
|
// With admin apikey, GET /memory/skills?loadable=false
|
||
|
|
// Returns draft skills from _drafts/ folder
|
||
|
|
|
||
|
|
let draft_skills = json!({
|
||
|
|
"skills": [
|
||
|
|
{
|
||
|
|
"name": "draft-experimental-feature",
|
||
|
|
"description": "WIP: experimental feature diagnosis",
|
||
|
|
"promoted_at": null,
|
||
|
|
"is_draft": true
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
let skill = &draft_skills["skills"][0];
|
||
|
|
assert!(skill["is_draft"].as_bool().unwrap_or(false));
|
||
|
|
assert!(skill["promoted_at"].is_null(), "Drafts have no promoted_at");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn s8_skill_count_metadata() {
|
||
|
|
// Response should include skill count
|
||
|
|
let response = json!({
|
||
|
|
"skills": [
|
||
|
|
{"name": "skill1"},
|
||
|
|
{"name": "skill2"},
|
||
|
|
{"name": "skill3"}
|
||
|
|
],
|
||
|
|
"total_count": 3,
|
||
|
|
"loaded_at": "2026-08-23T16:30:00Z"
|
||
|
|
});
|
||
|
|
|
||
|
|
let skills = response["skills"].as_array().unwrap();
|
||
|
|
let count = response["total_count"].as_u64().unwrap();
|
||
|
|
assert_eq!(skills.len(), count as usize);
|
||
|
|
}
|