mod common; use axum::http::StatusCode; use common::*; // ═══════════════════════════════════════════════════════════════════ // Relay chat input validation // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn relay_chat_missing_model() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "nomodel").await; let (status, _) = send( &app, post( "/api/v1/relay/chat/completions", &token, serde_json::json!({ "messages": [{ "role": "user", "content": "hello" }] }), ), ).await; assert_eq!(status, StatusCode::BAD_REQUEST, "missing model should be rejected"); } #[tokio::test] async fn relay_chat_empty_messages() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "emptymsg").await; let (status, _) = send( &app, post( "/api/v1/relay/chat/completions", &token, serde_json::json!({ "model": "test-model", "messages": [] }), ), ).await; assert_eq!(status, StatusCode::BAD_REQUEST, "empty messages should be rejected"); } #[tokio::test] async fn relay_chat_invalid_role() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "badrole").await; let (status, _) = send( &app, post( "/api/v1/relay/chat/completions", &token, serde_json::json!({ "model": "test-model", "messages": [{ "role": "invalid_role", "content": "hello" }] }), ), ).await; assert_ne!(status, StatusCode::OK, "invalid role should be rejected"); } #[tokio::test] async fn relay_chat_unauthenticated() { let (app, _pool) = build_test_app().await; let (status, _) = send( &app, post_public( "/api/v1/relay/chat/completions", serde_json::json!({ "model": "test-model", "messages": [{ "role": "user", "content": "hello" }] }), ), ).await; assert_eq!(status, StatusCode::UNAUTHORIZED, "unauthenticated relay request should be rejected"); } // ═══════════════════════════════════════════════════════════════════ // Relay model with real provider+model setup // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn relay_chat_with_disabled_provider() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "disabledprov").await; let user_token = register_token(&app, "disabledprovuser").await; // Create provider let (_, prov_body) = send( &app, post("/api/v1/providers", &admin, serde_json::json!({ "name": "disabled-prov", "display_name": "Disabled Prov", "base_url": "https://disabled.test/v1" }), ), ).await; let provider_id = prov_body["id"].as_str().unwrap(); // Create model send( &app, post("/api/v1/models", &admin, serde_json::json!({ "provider_id": provider_id, "model_id": "disabled-model", "alias": "Disabled Model", "context_window": 4096 }), ), ).await; // Add a key (so relay can try to use it) send( &app, post(&format!("/api/v1/providers/{provider_id}/keys"), &admin, serde_json::json!({ "key_label": "Test Key", "key_value": "sk-disabled-prov-key-1234567890" }), ), ).await; // Disable the provider send( &app, patch(&format!("/api/v1/providers/{provider_id}"), &admin, serde_json::json!({ "enabled": false }), ), ).await; // Chat request to disabled provider's model should fail let (status, _) = send( &app, post( "/api/v1/relay/chat/completions", &user_token, serde_json::json!({ "model": "disabled-model", "messages": [{ "role": "user", "content": "hello" }] }), ), ).await; // Should be NOT_FOUND (model not found) or FORBIDDEN (provider disabled) assert_ne!(status, StatusCode::OK, "disabled provider's model should not be usable"); } // ═══════════════════════════════════════════════════════════════════ // Relay model list includes configured models // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn relay_models_list_includes_active_models() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "listmodeladmin").await; let user_token = register_token(&app, "listmodeluser").await; // Create provider + model let (_, prov) = send( &app, post("/api/v1/providers", &admin, serde_json::json!({ "name": "list-prov", "display_name": "List Prov", "base_url": "https://list.test/v1" }), ), ).await; let pid = prov["id"].as_str().unwrap(); let keys_url = format!("/api/v1/providers/{pid}/keys"); send( &app, post(&keys_url, &admin, serde_json::json!({ "key_label": "K", "key_value": "sk-list-prov-key-abcdefghijklmnop" }), ), ).await; send( &app, post("/api/v1/models", &admin, serde_json::json!({ "provider_id": pid, "model_id": "listable-model", "alias": "Listable Model", "context_window": 8192 }), ), ).await; // List relay models as user let (status, body) = send(&app, get("/api/v1/relay/models", &user_token)).await; assert_eq!(status, StatusCode::OK, "relay models list should work: {body}"); let models = body.as_array().expect("should be array"); let found = models.iter().any(|m| m["model_id"] == "listable-model" || m["id"] == "listable-model"); assert!(found, "relay models should include the newly created model: {:?}", models); } // ═══════════════════════════════════════════════════════════════════ // Task access control: user sees only own tasks // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn relay_task_access_own_only() { let (app, _pool) = build_test_app().await; let token_a = register_token(&app, "taskuserA").await; let token_b = register_token(&app, "taskuserB").await; // Both list tasks — should not see each other's let (_, tasks_a) = send(&app, get("/api/v1/relay/tasks", &token_a)).await; let (_, tasks_b) = send(&app, get("/api/v1/relay/tasks", &token_b)).await; // Both should have empty task lists (no relay requests made) let a_items = if tasks_a.is_array() { tasks_a.as_array().unwrap().len() } else { tasks_a["items"].as_array().map(|v| v.len()).unwrap_or(0) }; let b_items = if tasks_b.is_array() { tasks_b.as_array().unwrap().len() } else { tasks_b["items"].as_array().map(|v| v.len()).unwrap_or(0) }; assert_eq!(a_items, 0, "user A should have 0 tasks"); assert_eq!(b_items, 0, "user B should have 0 tasks"); } #[tokio::test] async fn relay_models_require_auth() { let (app, _pool) = build_test_app().await; let req = axum::http::Request::builder() .uri("/api/v1/relay/models") .body(axum::body::Body::empty()) .unwrap(); let (status, _) = send(&app, req).await; assert_eq!(status, StatusCode::UNAUTHORIZED, "relay models should require authentication"); }