Files
poimen-memory/tests/agent_memory_api_platform_engineer.rs
T

549 lines
22 KiB
Rust
Raw Normal View History

// Integration test: Agent Memory with API Platform Engineer role requirements
// Tests contract-first design per agency-agents/engineering/engineering-api-platform-engineer.md
#[cfg(test)]
mod tests {
use serde_json::{json, Value};
// Test constants aligned with API Platform Engineer role
const API_VERSION: &str = "v1";
const PROJECT_ID: &str = "poimen";
const TEST_AGENT_ID: &str = "api-platform-engineer";
const API_PLATFORM_ENGINEER_ROLE: &str = "api-platform-engineer";
// API Platform Engineer role prompt templates
const CONTRACT_FIRST_PROMPT: &str = r#"
You are an API Platform Engineer designing a contract-first API.
Task: Review the following API specification for:
1. Naming consistency (pick snake_case or camelCase and never waver)
2. Backward compatibility (no breaking changes without versioning)
3. Error responses (consistent structure, stable codes, correct HTTP status semantics)
4. Rate limiting (communicated, not just enforced)
5. Documentation (SDKs and docs generated from spec, never drift)
Specification:
{{spec}}
Output JSON with:
{
"contract_valid": boolean,
"breaking_changes": [string],
"naming_inconsistencies": [string],
"error_issues": [string],
"rate_limit_issues": [string],
"recommendations": [string]
}
"#;
const BACKWARD_COMPATIBILITY_PROMPT: &str = r#"
You are an API versioning expert.
Analyze the proposed change:
{{change}}
Determine:
1. Is this a breaking change?
2. Does it require a new version?
3. What's the migration path?
4. What deprecation runway is needed?
Output JSON with:
{
"breaking": boolean,
"requires_new_version": boolean,
"migration_path": string,
"deprecation_runway_days": number,
"is_safe_additive": boolean
}
"#;
const SDK_GENERATION_PROMPT: &str = r#"
You are an SDK generation specialist.
Given this OpenAPI spec:
{{spec}}
Generate SDK requirements for:
1. Language: {{language}}
2. Idiomatic patterns for that language
3. Error handling
4. Retry logic and idempotency
5. Type safety
Output JSON with:
{
"sdk_structure": object,
"error_handling": string,
"idempotency_strategy": string,
"type_safety_level": string,
"generated_package_version": string
}
"#;
#[test]
fn test_contract_first_api_specification() {
// Contract-first principle: OpenAPI spec is source of truth
let api_spec = json!({
"openapi": "3.0.0",
"info": {
"title": "Poimen Agent Memory API",
"version": API_VERSION,
"description": "Agent memory with role-to-prompt mapping"
},
"paths": {
"/memory/agents/{project_id}/prompts": {
"post": {
"operationId": "createPrompt",
"parameters": [
{
"name": "project_id",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name", "template", "task_category"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"template": { "type": "string", "description": "Prompt template with {{placeholders}}" },
"target_model": { "type": "string", "example": "ornith:35b" },
"task_category": { "type": "string", "enum": ["extraction", "reasoning", "summarization", "validation"] },
"tags": { "type": "array", "items": { "type": "string" } }
}
}
}
}
},
"responses": {
"201": {
"description": "Prompt created",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/Prompt" }
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"429": { "$ref": "#/components/responses/RateLimited" }
}
}
},
"/memory/agents/{project_id}/roles": {
"post": {
"operationId": "mapRoleToPrompt",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["role_name", "prompt_id"],
"properties": {
"role_name": { "type": "string", "minLength": 1 },
"prompt_id": { "type": "string", "format": "uuid" },
"priority": { "type": "integer", "default": 0 }
}
}
}
}
},
"responses": {
"200": { "description": "Mapping created" },
"400": { "$ref": "#/components/responses/BadRequest" }
}
}
},
"/memory/agents/{project_id}/roles/{role_name}/prompts": {
"get": {
"operationId": "getRolePrompts",
"responses": {
"200": { "description": "List of prompts for role" },
"404": { "$ref": "#/components/responses/NotFound" }
}
}
}
},
"components": {
"schemas": {
"Prompt": {
"type": "object",
"required": ["id", "name", "template", "task_category"],
"properties": {
"id": { "type": "string", "format": "uuid" },
"name": { "type": "string" },
"template": { "type": "string" },
"target_model": { "type": "string", "nullable": true },
"task_category": { "type": "string" },
"usage_count": { "type": "integer" },
"avg_quality": { "type": "number", "format": "float" },
"version": { "type": "integer" },
"created_at": { "type": "string", "format": "date-time" }
}
},
"Error": {
"type": "object",
"required": ["code", "message"],
"properties": {
"code": { "type": "string", "description": "Machine-readable error code" },
"message": { "type": "string", "description": "Human-readable error message" },
"details": { "type": "object", "description": "Field-level or contextual detail" },
"request_id": { "type": "string", "description": "Trace this to support" }
}
}
},
"responses": {
"BadRequest": {
"description": "Bad request",
"content": {
"application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
}
},
"NotFound": {
"description": "Resource not found",
"content": {
"application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
}
},
"RateLimited": {
"description": "Rate limited",
"headers": {
"Retry-After": { "schema": { "type": "integer" } },
"X-RateLimit-Limit": { "schema": { "type": "integer" } },
"X-RateLimit-Remaining": { "schema": { "type": "integer" } },
"X-RateLimit-Reset": { "schema": { "type": "integer" } }
},
"content": {
"application/json": { "schema": { "$ref": "#/components/schemas/Error" } }
}
}
}
}
});
// Validate contract structure
assert_eq!(api_spec["openapi"], "3.0.0");
assert_eq!(api_spec["info"]["version"], API_VERSION);
// Validate error schema is consistent
let error_schema = &api_spec["components"]["schemas"]["Error"];
assert!(error_schema["required"].as_array().unwrap().contains(&Value::String("code".to_string())));
assert!(error_schema["required"].as_array().unwrap().contains(&Value::String("message".to_string())));
// Validate naming consistency (snake_case)
assert!(api_spec["paths"]["/memory/agents/{project_id}/prompts"]["post"]["operationId"].as_str().unwrap().contains("createPrompt"));
assert!(api_spec["paths"]["/memory/agents/{project_id}/roles/{role_name}/prompts"]["get"]["operationId"].as_str().unwrap().contains("getRolePrompts"));
// Validate backward compatibility: all fields are optional except required ones
let create_prompt_schema = &api_spec["paths"]["/memory/agents/{project_id}/prompts"]["post"]["requestBody"]["content"]["application/json"]["schema"];
assert_eq!(
create_prompt_schema["required"].as_array().unwrap(),
&vec![
Value::String("name".to_string()),
Value::String("template".to_string()),
Value::String("task_category".to_string())
]
);
println!("✓ Contract-first API specification validated");
}
#[test]
fn test_backward_compatibility_rules() {
// Rule 1: Adding optional fields is safe
let safe_change = json!({
"type": "add_field",
"field": "metadata",
"required": false,
"breaking": false
});
assert!(!safe_change["breaking"].as_bool().unwrap());
// Rule 2: Removing fields is breaking
let breaking_change = json!({
"type": "remove_field",
"field": "template",
"breaking": true,
"requires_version_bump": true
});
assert!(breaking_change["breaking"].as_bool().unwrap());
assert!(breaking_change["requires_version_bump"].as_bool().unwrap());
// Rule 3: Adding new enum value is safe if clients tolerate unknowns
let safe_enum_addition = json!({
"type": "add_enum_value",
"enum": "task_category",
"new_value": "planning",
"breaking": false,
"requires_documentation": true
});
assert!(!safe_enum_addition["breaking"].as_bool().unwrap());
// Rule 4: Changing field type is breaking
let breaking_type_change = json!({
"type": "change_field_type",
"field": "usage_count",
"old_type": "integer",
"new_type": "string",
"breaking": true,
"requires_version_bump": true,
"migration_path": "Convert all consumers to parse as string"
});
assert!(breaking_type_change["breaking"].as_bool().unwrap());
println!("✓ Backward compatibility rules validated");
}
#[test]
fn test_rate_limiting_communication() {
// Rate limits must be communicated in response headers
let response_headers = json!({
"X-RateLimit-Limit": 1000,
"X-RateLimit-Remaining": 847,
"X-RateLimit-Reset": 1720483200,
"Retry-After": 30
});
// All required rate limit headers present
assert!(response_headers.get("X-RateLimit-Limit").is_some());
assert!(response_headers.get("X-RateLimit-Remaining").is_some());
assert!(response_headers.get("X-RateLimit-Reset").is_some());
// On 429, Retry-After present
let rate_limited_response = json!({
"status": 429,
"error": {
"code": "rate_limit_exceeded",
"message": "1000 req/hr exceeded; retry after 30s",
"request_id": "req_a1b2"
},
"headers": {
"Retry-After": 30
}
});
assert_eq!(rate_limited_response["status"], 429);
assert_eq!(rate_limited_response["error"]["code"], "rate_limit_exceeded");
assert!(rate_limited_response["headers"]["Retry-After"].as_i64().unwrap() > 0);
println!("✓ Rate limiting communication validated");
}
#[test]
fn test_error_response_consistency() {
// Error responses must have consistent structure everywhere
let errors = vec![
json!({
"code": "invalid_request",
"message": "name field required",
"details": { "field": "name" },
"request_id": "req-123"
}),
json!({
"code": "not_found",
"message": "Prompt not found",
"details": { "prompt_id": "uuid-456" },
"request_id": "req-789"
}),
json!({
"code": "permission_denied",
"message": "Insufficient capabilities",
"details": { "required": "memory:write" },
"request_id": "req-999"
}),
];
for error in errors {
// All errors have required structure
assert!(error["code"].is_string());
assert!(error["message"].is_string());
assert!(error["request_id"].is_string());
// No 200 with error (must use proper HTTP status)
assert_ne!(error["code"], ""); // code is stable, machine-readable
}
println!("✓ Error response consistency validated");
}
#[test]
fn test_deprecation_lifecycle() {
// Deprecation requires: Announce → Signal → Runway → Monitor → Sunset
let deprecation_plan = json!({
"endpoint": "/agents/{id}",
"lifecycle": {
"phase": "announced",
"deprecation_date": "2025-06-01",
"sunset_date": "2026-06-01",
"runway_days": 365
},
"signals": {
"deprecation_header": "Deprecation: true",
"sunset_header": "Sunset: Sun, 01 Jun 2026 00:00:00 GMT",
"warning_in_response": true
},
"migration_guide": "Use /agents/v2/{id} instead",
"monitoring": {
"track_usage_by_consumer": true,
"alert_on_remaining_usage": true
}
});
assert_eq!(deprecation_plan["lifecycle"]["runway_days"], 365);
assert!(deprecation_plan["signals"]["deprecation_header"].as_str().unwrap().contains("Deprecation"));
assert!(deprecation_plan["monitoring"]["track_usage_by_consumer"].as_bool().unwrap());
println!("✓ Deprecation lifecycle validated");
}
#[test]
fn test_idempotency_and_retry_safety() {
// Write operations must be idempotent via Idempotency-Key
let request_with_key = json!({
"method": "POST",
"path": "/memory/agents/project1/prompts",
"headers": {
"Idempotency-Key": "req-unique-uuid-123"
},
"body": {
"name": "extract-entities",
"template": "Extract entities from {{text}}"
}
});
assert!(request_with_key["headers"]["Idempotency-Key"].is_string());
// Retry with same key returns cached response
let response_1 = json!({
"status": 201,
"id": "prompt-uuid-456"
});
let response_2_retry = json!({
"status": 201,
"id": "prompt-uuid-456",
"cached": true
});
// Both return same result → safe to retry
assert_eq!(response_1["id"], response_2_retry["id"]);
println!("✓ Idempotency and retry safety validated");
}
#[test]
fn test_api_platform_engineer_role_requirements() {
// Comprehensive validation per api-platform-engineer.md role
let role_requirements = json!({
"role": API_PLATFORM_ENGINEER_ROLE,
"requirements": {
"contract_first": {
"openapi_spec": "required",
"source_of_truth_before_code": true,
"consistency_reviewed": true
},
"backward_compatibility": {
"no_silent_breaking_changes": true,
"additive_changes_allowed": true,
"versioning_policy": "major version in path (/v1, /v2)",
"deprecation_runway": "6-12+ months"
},
"error_handling": {
"consistent_structure": true,
"stable_machine_readable_code": true,
"correct_http_status": true,
"request_id_for_tracing": true
},
"rate_limiting": {
"communicated_headers": true,
"no_ambush_429": true,
"retry_after_provided": true
},
"sdk_and_docs": {
"generated_from_spec": true,
"never_drift": true,
"typed_idiomatic": true,
"multiple_languages": true
},
"idempotency": {
"write_operations_idempotent": true,
"idempotency_key_support": true,
"safe_retry": true
}
}
});
// Validate all requirements
assert!(role_requirements["requirements"]["contract_first"]["openapi_spec"] == "required");
assert!(role_requirements["requirements"]["backward_compatibility"]["no_silent_breaking_changes"].as_bool().unwrap());
assert!(role_requirements["requirements"]["error_handling"]["consistent_structure"].as_bool().unwrap());
assert!(role_requirements["requirements"]["rate_limiting"]["communicated_headers"].as_bool().unwrap());
assert!(role_requirements["requirements"]["sdk_and_docs"]["generated_from_spec"].as_bool().unwrap());
assert!(role_requirements["requirements"]["idempotency"]["write_operations_idempotent"].as_bool().unwrap());
println!("✓ API Platform Engineer role requirements validated");
}
#[test]
fn test_agent_prompts_for_api_platform_engineer() {
// Agent prompts aligned with API Platform Engineer role
let agent_prompts = vec![
("contract-review", CONTRACT_FIRST_PROMPT, "extraction"),
("compatibility-check", BACKWARD_COMPATIBILITY_PROMPT, "reasoning"),
("sdk-generation", SDK_GENERATION_PROMPT, "generation"),
];
for (name, template, category) in agent_prompts {
let prompt = json!({
"name": name,
"template": template,
"task_category": category,
"target_model": "ornith:35b"
});
assert!(!prompt["template"].as_str().unwrap().is_empty());
assert!(prompt["template"].as_str().unwrap().contains("{{") || prompt["template"].as_str().unwrap().contains("output"));
}
println!("✓ Agent prompts for API Platform Engineer validated");
}
#[test]
fn test_role_to_prompt_mapping_consistency() {
// Role mappings ensure consistent prompt selection
let role_mappings = json!({
"api-platform-engineer": [
{
"prompt": "contract-review",
"priority": 1,
"for_task": "API specification review"
},
{
"prompt": "compatibility-check",
"priority": 2,
"for_task": "Breaking change validation"
},
{
"prompt": "sdk-generation",
"priority": 3,
"for_task": "SDK generation planning"
}
]
});
let engineer_prompts = role_mappings["api-platform-engineer"].as_array().unwrap();
assert_eq!(engineer_prompts.len(), 3);
// Prompts ordered by priority
assert!(engineer_prompts[0]["priority"].as_i64().unwrap() < engineer_prompts[1]["priority"].as_i64().unwrap());
println!("✓ Role-to-prompt mapping consistency validated");
}
}