mod common; use axum::http::StatusCode; use common::*; // ═══════════════════════════════════════════════════════════════════ // Config analysis // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_analysis_empty() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "cfganalyze").await; let (status, body) = send(&app, get("/api/v1/config/analysis", &token)).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["total_items"], 0); } // ═══════════════════════════════════════════════════════════════════ // Config items CRUD // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_items_crud() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "cfgadmin").await; // Create config item let (status, body) = send( &app, post( "/api/v1/config/items", &admin, serde_json::json!({ "category": "server", "key_path": "server.host", "value_type": "string", "current_value": "0.0.0.0", "description": "Server bind address" }), ), ).await; assert_eq!(status, StatusCode::CREATED, "create config item: {body}"); let item_id = body["id"].as_str().unwrap(); // List let (status, list) = send(&app, get("/api/v1/config/items", &admin)).await; assert_eq!(status, StatusCode::OK); assert!(list.is_array() || list["items"].is_array()); // Get let (status, body) = send(&app, get(&format!("/api/v1/config/items/{item_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["key_path"], "server.host"); // Update let (status, body) = send( &app, put( &format!("/api/v1/config/items/{item_id}"), &admin, serde_json::json!({ "current_value": "127.0.0.1" }), ), ).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["current_value"], "127.0.0.1"); // Delete let (status, _) = send(&app, delete(&format!("/api/v1/config/items/{item_id}"), &admin)).await; assert_eq!(status, StatusCode::OK); } #[tokio::test] async fn config_items_write_forbidden_for_user() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "cfguser").await; let (status, _) = send( &app, post( "/api/v1/config/items", &token, serde_json::json!({ "category": "x", "key_path": "y", "value_type": "string" }), ), ).await; assert_eq!(status, StatusCode::FORBIDDEN); } // ═══════════════════════════════════════════════════════════════════ // Config seed // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_seed_admin_only() { let (app, _pool) = build_test_app().await; let user_token = register_token(&app, "cfgseeduser").await; let (status, _) = send(&app, post("/api/v1/config/seed", &user_token, serde_json::json!({}))).await; assert_eq!(status, StatusCode::FORBIDDEN); } // ═══════════════════════════════════════════════════════════════════ // Config sync (push) // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_sync_push() { let (app, pool) = build_test_app().await; let admin = admin_token(&app, &pool, "cfgsync").await; let (status, body) = send( &app, post( "/api/v1/config/sync", &admin, serde_json::json!({ "client_fingerprint": "test-desktop-v1", "action": "push", "config_keys": ["server.host", "server.port"], "client_values": { "server.host": "192.168.1.1", "server.port": "9090" } }), ), ).await; assert_eq!(status, StatusCode::OK, "config sync push: {body}"); // Push mode: keys don't exist in SaaS → auto-created assert_eq!(body["created"], 2); } // ═══════════════════════════════════════════════════════════════════ // Config diff // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_diff() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "cfgdiff").await; let (status, body) = send( &app, post( "/api/v1/config/diff", &token, serde_json::json!({ "client_fingerprint": "test-client", "action": "push", "config_keys": ["server.host"], "client_values": { "server.host": "0.0.0.0" } }), ), ).await; assert_eq!(status, StatusCode::OK); assert_eq!(body["total_keys"], 1); assert!(body["items"].is_array()); } // ═══════════════════════════════════════════════════════════════════ // Config sync logs // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_sync_logs() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "cfglogs").await; let (status, _) = send(&app, get("/api/v1/config/sync-logs", &token)).await; assert_eq!(status, StatusCode::OK); } // ═══════════════════════════════════════════════════════════════════ // Config pull // ═══════════════════════════════════════════════════════════════════ #[tokio::test] async fn config_pull_empty() { let (app, _pool) = build_test_app().await; let token = register_token(&app, "cfgpull").await; let (status, _) = send(&app, get("/api/v1/config/pull", &token)).await; assert_eq!(status, StatusCode::OK); }