232 lines
6.0 KiB
Rust
232 lines
6.0 KiB
Rust
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<AppState>) -> 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<AppState>,
|
||
|
|
) -> 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<AppState>,
|
||
|
|
) -> 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<AppState>,
|
||
|
|
) -> 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);
|
||
|
|
}
|