231 lines
7.2 KiB
Rust
231 lines
7.2 KiB
Rust
use serde_json::json;
|
|||
|
|
|
||
|
|
// ============================================================================
|
||
|
|
// M3.5.6 — GET /projects and /projects/{id}/status: project metadata
|
||
|
|
// ============================================================================
|
||
|
|
//
|
||
|
|
// 8 tests covering:
|
||
|
|
// - List all projects
|
||
|
|
// - Project summary fields
|
||
|
|
// - Individual project status
|
||
|
|
// - Standing queries per project
|
||
|
|
// - L2 synthesis metrics
|
||
|
|
// - Memory size calculations
|
||
|
|
// - Timestamp accuracy
|
||
|
|
// - Last activity tracking
|
||
|
|
//
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p1_list_all_projects() {
|
||
|
|
// GET /memory/projects
|
||
|
|
// Returns array of projects with summary metadata
|
||
|
|
|
||
|
|
let projects = json!({
|
||
|
|
"projects": [
|
||
|
|
{
|
||
|
|
"id": "poimen",
|
||
|
|
"standing_queries": 3,
|
||
|
|
"last_ingest_at": "2026-08-20T10:30:00Z",
|
||
|
|
"last_synthesis_at": "2026-08-20T12:00:00Z",
|
||
|
|
"total_chunks": 412,
|
||
|
|
"total_evidence": 17,
|
||
|
|
"memory_size_bytes": 45280
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "agent-rust",
|
||
|
|
"standing_queries": 2,
|
||
|
|
"last_ingest_at": "2026-08-21T08:15:00Z",
|
||
|
|
"last_synthesis_at": "2026-08-21T09:45:00Z",
|
||
|
|
"total_chunks": 198,
|
||
|
|
"total_evidence": 8,
|
||
|
|
"memory_size_bytes": 22140
|
||
|
|
}
|
||
|
|
]
|
||
|
|
});
|
||
|
|
|
||
|
|
assert_eq!(projects["projects"].as_array().unwrap().len(), 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p2_project_summary_fields_present() {
|
||
|
|
// Each project in list must have:
|
||
|
|
// - id
|
||
|
|
// - standing_queries (count)
|
||
|
|
// - last_ingest_at (ISO 8601 or null)
|
||
|
|
// - last_synthesis_at (ISO 8601 or null)
|
||
|
|
// - total_chunks
|
||
|
|
// - total_evidence
|
||
|
|
// - memory_size_bytes
|
||
|
|
|
||
|
|
let project = json!({
|
||
|
|
"id": "poimen",
|
||
|
|
"standing_queries": 3,
|
||
|
|
"last_ingest_at": "2026-08-20T10:30:00Z",
|
||
|
|
"last_synthesis_at": "2026-08-20T12:00:00Z",
|
||
|
|
"total_chunks": 412,
|
||
|
|
"total_evidence": 17,
|
||
|
|
"memory_size_bytes": 45280
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(project["id"].is_string());
|
||
|
|
assert!(project["standing_queries"].is_number());
|
||
|
|
assert!(project["last_ingest_at"].is_string() || project["last_ingest_at"].is_null());
|
||
|
|
assert!(project["last_synthesis_at"].is_string() || project["last_synthesis_at"].is_null());
|
||
|
|
assert!(project["total_chunks"].is_number());
|
||
|
|
assert!(project["total_evidence"].is_number());
|
||
|
|
assert!(project["memory_size_bytes"].is_number());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p3_individual_project_status() {
|
||
|
|
// GET /memory/projects/poimen/status
|
||
|
|
// Returns detailed project state including standing queries and synthesis
|
||
|
|
|
||
|
|
let status = json!({
|
||
|
|
"project_id": "poimen",
|
||
|
|
"standing_queries": [
|
||
|
|
{
|
||
|
|
"id": "infra-root-causes",
|
||
|
|
"question": "What infrastructure bugs were found...",
|
||
|
|
"last_ingest_at": "2026-08-20T10:30:00Z",
|
||
|
|
"chunks_seen": 412,
|
||
|
|
"chunks_used": 17,
|
||
|
|
"memory_tokens": 142
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"id": "tool-failures",
|
||
|
|
"question": "Which tools failed and what was the workaround...",
|
||
|
|
"last_ingest_at": "2026-08-20T09:00:00Z",
|
||
|
|
"chunks_seen": 412,
|
||
|
|
"chunks_used": 5,
|
||
|
|
"memory_tokens": 45
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"l2_synthesis": {
|
||
|
|
"last_synthesis_at": "2026-08-20T12:00:00Z",
|
||
|
|
"chunks_seen": 3,
|
||
|
|
"chunks_used": 2,
|
||
|
|
"memory_tokens": 876,
|
||
|
|
"exit_gate_fired": true
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
assert_eq!(status["project_id"], "poimen");
|
||
|
|
assert!(status["standing_queries"].is_array());
|
||
|
|
assert!(status["l2_synthesis"].is_object());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p4_standing_queries_per_project() {
|
||
|
|
// Each standing query should have:
|
||
|
|
// - id
|
||
|
|
// - question
|
||
|
|
// - last_ingest_at
|
||
|
|
// - chunks_seen (total processed)
|
||
|
|
// - chunks_used (passed gate)
|
||
|
|
// - memory_tokens (current L1+L2 tokens)
|
||
|
|
|
||
|
|
let query = json!({
|
||
|
|
"id": "infra-root-causes",
|
||
|
|
"question": "What infrastructure bugs were found...",
|
||
|
|
"last_ingest_at": "2026-08-20T10:30:00Z",
|
||
|
|
"chunks_seen": 412,
|
||
|
|
"chunks_used": 17,
|
||
|
|
"memory_tokens": 142
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(query["id"].is_string());
|
||
|
|
assert!(query["question"].is_string());
|
||
|
|
assert!(query["last_ingest_at"].is_string() || query["last_ingest_at"].is_null());
|
||
|
|
assert!(query["chunks_seen"].is_number());
|
||
|
|
assert!(query["chunks_used"].is_number());
|
||
|
|
assert!(query["memory_tokens"].is_number());
|
||
|
|
|
||
|
|
// chunks_used should be <= chunks_seen (gate discrimination)
|
||
|
|
let seen = query["chunks_seen"].as_u64().unwrap();
|
||
|
|
let used = query["chunks_used"].as_u64().unwrap();
|
||
|
|
assert!(used <= seen, "chunks_used {} > chunks_seen {}", used, seen);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p5_l2_synthesis_metrics() {
|
||
|
|
// L2 synthesis block should have:
|
||
|
|
// - last_synthesis_at
|
||
|
|
// - chunks_seen (inputs to synthesis)
|
||
|
|
// - chunks_used (outputs generated)
|
||
|
|
// - memory_tokens (L2 tokens in vault)
|
||
|
|
// - exit_gate_fired (boolean: did synthesis conclude?)
|
||
|
|
|
||
|
|
let l2 = json!({
|
||
|
|
"last_synthesis_at": "2026-08-20T12:00:00Z",
|
||
|
|
"chunks_seen": 3,
|
||
|
|
"chunks_used": 2,
|
||
|
|
"memory_tokens": 876,
|
||
|
|
"exit_gate_fired": true
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(l2["last_synthesis_at"].is_string() || l2["last_synthesis_at"].is_null());
|
||
|
|
assert!(l2["chunks_seen"].is_number());
|
||
|
|
assert!(l2["chunks_used"].is_number());
|
||
|
|
assert!(l2["memory_tokens"].is_number());
|
||
|
|
assert!(l2["exit_gate_fired"].is_boolean());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p6_memory_size_bytes_calculation() {
|
||
|
|
// memory_size_bytes should reflect actual vault size
|
||
|
|
// Rough estimate: average chunk = ~500 bytes + metadata
|
||
|
|
// 100 chunks ≈ 50-60KB
|
||
|
|
|
||
|
|
let projects = vec![
|
||
|
|
("poimen", 412, 45280), // 412 chunks = 45KB
|
||
|
|
("agent-rust", 198, 22140), // 198 chunks = 22KB
|
||
|
|
("workflows", 85, 9500), // 85 chunks = 9.5KB
|
||
|
|
];
|
||
|
|
|
||
|
|
for (_name, chunks, bytes) in projects {
|
||
|
|
let bytes_per_chunk = bytes as f32 / chunks as f32;
|
||
|
|
assert!(
|
||
|
|
bytes_per_chunk > 100.0 && bytes_per_chunk < 1000.0,
|
||
|
|
"Bytes per chunk {} seems off for {} chunks",
|
||
|
|
bytes_per_chunk,
|
||
|
|
chunks
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p7_timestamp_ordering() {
|
||
|
|
// last_ingest_at should be >= last_synthesis_at
|
||
|
|
// (synthesis runs after ingest)
|
||
|
|
|
||
|
|
let project = json!({
|
||
|
|
"id": "poimen",
|
||
|
|
"last_ingest_at": "2026-08-20T10:30:00Z",
|
||
|
|
"last_synthesis_at": "2026-08-20T12:00:00Z",
|
||
|
|
});
|
||
|
|
|
||
|
|
// Parsing would be done by HTTP handler
|
||
|
|
// Here we just verify structure
|
||
|
|
assert!(project["last_ingest_at"].is_string());
|
||
|
|
assert!(project["last_synthesis_at"].is_string());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[test]
|
||
|
|
fn p8_project_status_latency_included() {
|
||
|
|
// Response should include latency_ms for introspection calls
|
||
|
|
// Helps identify slow queries
|
||
|
|
|
||
|
|
let status = json!({
|
||
|
|
"project_id": "poimen",
|
||
|
|
"standing_queries": [],
|
||
|
|
"l2_synthesis": {},
|
||
|
|
"latency_ms": 45,
|
||
|
|
"queried_at": "2026-08-23T16:30:00Z"
|
||
|
|
});
|
||
|
|
|
||
|
|
assert!(status["latency_ms"].is_number());
|
||
|
|
assert!(status["latency_ms"].as_u64().unwrap() >= 0);
|
||
|
|
assert!(status["queried_at"].is_string());
|
||
|
|
}
|