use actix_web::{web, App, HttpServer, HttpResponse, test}; use serde_json::json; use std::sync::{Arc, Mutex}; use std::time::Instant; /// Server state. struct AppState { pub api_key: String, pub start_time: Instant, } /// Health check endpoint. async fn health_check(state: web::Data) -> HttpResponse { let uptime = state.start_time.elapsed().as_secs(); HttpResponse::Ok() .json(json!({"status": "ok", "uptime_seconds": uptime})) } /// Check auth helper. fn check_auth(api_key: Option<&str>, expected: &str) -> Result<(), HttpResponse> { if api_key != Some(expected) { return Err(HttpResponse::Unauthorized() .json(json!({"error": "unauthorized", "reason": "missing apikey header"}))); } Ok(()) } /// Ingest endpoint. async fn ingest_handler( req: actix_web::HttpRequest, state: web::Data, ) -> HttpResponse { let api_key = req.headers() .get("apikey") .and_then(|h| h.to_str().ok()); if let Err(e) = check_auth(api_key, &state.api_key) { return e; } HttpResponse::Accepted() .json(json!({"status": "ok", "job_id": "job-001"})) } /// Query endpoint. async fn query_handler( req: actix_web::HttpRequest, state: web::Data, ) -> HttpResponse { let api_key = req.headers() .get("apikey") .and_then(|h| h.to_str().ok()); if let Err(e) = check_auth(api_key, &state.api_key) { return e; } HttpResponse::Ok() .json(json!({"status": "ok", "results": []})) } /// Skills endpoint. async fn skills_handler( req: actix_web::HttpRequest, state: web::Data, ) -> HttpResponse { let api_key = req.headers() .get("apikey") .and_then(|h| h.to_str().ok()); if let Err(e) = check_auth(api_key, &state.api_key) { return e; } HttpResponse::Ok() .json(json!({"status": "ok", "skills": []})) } #[actix_web::test] async fn a1_server_starts() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state) .route("/health", web::get().to(health_check)) ).await; let req = test::TestRequest::get() .uri("/health") .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); } #[actix_web::test] async fn a2_health_check() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state) .route("/health", web::get().to(health_check)) ).await; let req = test::TestRequest::get() .uri("/health") .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 200); let body = test::read_body(resp).await; let body_str = String::from_utf8(body.to_vec()).unwrap(); assert!(body_str.contains("ok")); } #[actix_web::test] async fn a3_auth_missing_is_401() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state) .route("/memory/skills", web::get().to(skills_handler)) ).await; // No apikey header let req = test::TestRequest::get() .uri("/memory/skills") .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 401); } #[actix_web::test] async fn a4_auth_wrong_is_401() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state) .route("/memory/skills", web::get().to(skills_handler)) ).await; // Wrong apikey let req = test::TestRequest::get() .uri("/memory/skills") .append_header(("apikey", "wrong")) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 401); } #[actix_web::test] async fn a5_auth_correct_passes() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state) .route("/memory/skills", web::get().to(skills_handler)) ).await; // Correct apikey let req = test::TestRequest::get() .uri("/memory/skills") .append_header(("apikey", "test-key")) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 200); } #[actix_web::test] async fn a7_three_routes_exist() { let state = web::Data::new(AppState { api_key: "test-key".to_string(), start_time: Instant::now(), }); let app = test::init_service( App::new() .app_data(state.clone()) .route("/memory/ingest", web::post().to(ingest_handler)) .route("/memory/query", web::get().to(query_handler)) .route("/memory/skills", web::get().to(skills_handler)) ).await; // Test ingest let req = test::TestRequest::post() .uri("/memory/ingest") .append_header(("apikey", "test-key")) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 202); // Test query let req = test::TestRequest::get() .uri("/memory/query") .append_header(("apikey", "test-key")) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 200); // Test skills let req = test::TestRequest::get() .uri("/memory/skills") .append_header(("apikey", "test-key")) .to_request(); let resp = test::call_service(&app, req).await; assert_eq!(resp.status(), 200); }