mod common; use axum::http::StatusCode; use common::*; // ═══════════════════════════════════════════════════════════════════ // Provider CRUD // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn provider_crud_full_lifecycle() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "provadmin").await; // Create let (status, body) = send( &app, post( "/api/v1/providers", &admin, serde_json::json!({ "name": "test-provider", "display_name": "Test Provider", "base_url": "https://api.example.com/v1" }), ), ).await; assert_eq!(status, StatusCode::CREATED, "create provider failed: {body}"); let provider_id = body["id"].as_str().unwrap().to_string(); // List (paginated) let (status, body) = send(&app, get("/api/v1/providers", &admin)).await; assert_eq!(status, StatusCode::OK); let items = body["items"].as_array().expect("providers should be paginated {items}"); assert!(items.iter().any(|p| p["id"] == provider_id)); // Get let (status, body) = send(&app, get(&format!("/api/v1/providers/{provider_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["name"], "test-provider"); // Update let (status, body) = send( &app, patch( &format!("/api/v1/providers/{provider_id}"), &admin, serde_json::json!({ "display_name": "Updated Provider" }), ), ).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["display_name"], "Updated Provider"); // Delete let (status, _) = send(&app, delete(&format!("/api/v1/providers/{provider_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); // Verify deleted let (status, _) = send(&app, get(&format!("/api/v1/providers/{provider_id}"), &admin)).await; assert_eq!(status, StatusCode::NOT_FOUND); } #[tokio::test] async fn provider_create_forbidden_for_user() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "provuser").await; let (status, _) = send( &app, post( "/api/v1/providers", &token, serde_json::json!({ "name": "x", "display_name": "X", "base_url": "https://x.com" }), ), ).await; assert_eq!(status, StatusCode::FORBIDDEN); } #[tokio::test] async fn provider_list_accessible_to_all_authenticated() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "listprovuser").await; let (status, body) = send(&app, get("/api/v1/providers", &token)).await; assert_eq!(status, StatusCode::OK); assert!(body["items"].is_array(), "providers list should be paginated: {body}"); } // ═══════════════════════════════════════════════════════════════════ // Model CRUD // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn model_crud_with_provider() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "modeladmin").await; // Create provider first let (_, prov_body) = send( &app, post( "/api/v1/providers", &admin, serde_json::json!({ "name": "model-prov", "display_name": "Model Prov", "base_url": "https://api.test.com/v1" }), ), ).await; let provider_id = prov_body["id"].as_str().unwrap(); // Create model let (status, body) = send( &app, post( "/api/v1/models", &admin, serde_json::json!({ "provider_id": provider_id, "model_id": "test-model-v1", "alias": "Test Model", "context_window": 8192 }), ), ).await; assert_eq!(status, StatusCode::CREATED, "create model: {body}"); let model_id = body["id"].as_str().unwrap(); // List models (paginated) let (status, list) = send(&app, get("/api/v1/models", &admin)).await; assert_eq!(status, StatusCode::OK); assert!(list["items"].is_array(), "models list should be paginated: {list}"); // Get model let (status, _) = send(&app, get(&format!("/api/v1/models/{model_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); // Update model let (status, body) = send( &app, patch( &format!("/api/v1/models/{model_id}"), &admin, serde_json::json!({ "alias": "Updated Alias" }), ), ).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["alias"], "Updated Alias"); // Delete model let (status, _) = send(&app, delete(&format!("/api/v1/models/{model_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); } #[tokio::test] async fn model_create_forbidden_for_user() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "modeluser").await; let (status, _) = send( &app, post( "/api/v1/models", &token, serde_json::json!({ "provider_id": "x", "model_id": "y", "alias": "Z" }), ), ).await; assert_eq!(status, StatusCode::FORBIDDEN); } // ═══════════════════════════════════════════════════════════════════ // Account API Key // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn api_key_requires_existing_provider() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "keyuser").await; let (status, _) = send( &app, post( "/api/v1/keys", &token, serde_json::json!({ "provider_id": "nonexistent", "key_value": "sk-test", "key_label": "Test" }), ), ).await; assert_eq!(status, StatusCode::NOT_FOUND); } #[tokio::test] async fn api_key_lifecycle_with_provider() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "keyadmin").await; // Create provider let (_, prov) = send( &app, post( "/api/v1/providers", &admin, serde_json::json!({ "name": "key-prov", "display_name": "Key Prov", "base_url": "https://api.test.com/v1" }), ), ).await; let provider_id = prov["id"].as_str().unwrap(); // Create key as regular user let user_token = register_token(&app, "keyowner").await; let (status, body) = send( &app, post( "/api/v1/keys", &user_token, serde_json::json!({ "provider_id": provider_id, "key_value": "sk-test-key-123", "key_label": "My Key" }), ), ).await; assert_eq!(status, StatusCode::CREATED, "create key: {body}"); let key_id = body["id"].as_str().unwrap(); // List keys (paginated) let (status, list) = send(&app, get("/api/v1/keys", &user_token)).await; assert_eq!(status, StatusCode::OK); assert!(list["items"].is_array(), "keys list should be paginated: {list}"); // Delete key let (status, _) = send(&app, delete(&format!("/api/v1/keys/{key_id}"), &user_token)).await; assert_eq!(status, StatusCode::OK); } // ═══════════════════════════════════════════════════════════════════ // Usage stats // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn usage_stats_empty() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "usageuser").await; let (status, body) = send(&app, get("/api/v1/usage", &token)).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["total_requests"], 0); }