// 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"); } }