1use crate::k8s::KubernetesClient;
2use axum::{extract::Path, http::StatusCode, response::Json, Json as JsonBody};
3use serde::{Deserialize, Serialize};
4use sparktest_core::*;
5use uuid::Uuid;
6
7#[derive(Serialize)]
8pub struct HealthResponse {
9 pub status: String,
10 pub timestamp: String,
11}
12
13#[derive(Deserialize)]
14pub struct CreateRunRequest {
15 pub name: String,
16 pub image: String,
17 pub commands: Vec<String>,
18}
19
20pub async fn health_check() -> Json<HealthResponse> {
21 Json(HealthResponse {
22 status: "healthy".to_string(),
23 timestamp: chrono::Utc::now().to_rfc3339(),
24 })
25}
26
27pub async fn get_runs() -> Result<Json<Vec<TestRun>>, StatusCode> {
28 Ok(Json(vec![]))
30}
31
32pub async fn create_run(
33 JsonBody(req): JsonBody<CreateRunRequest>,
34) -> Result<Json<TestRun>, StatusCode> {
35 let run = TestRun {
37 id: Uuid::new_v4(),
38 name: req.name,
39 image: req.image,
40 commands: req.commands,
41 status: "pending".to_string(),
42 created_at: chrono::Utc::now(),
43 definition_id: None,
44 executor_id: None,
45 suite_id: None,
46 variables: None,
47 artifacts: None,
48 duration: None,
49 retries: None,
50 logs: None,
51 k8s_job_name: None,
52 pod_scheduled: None,
53 container_created: None,
54 container_started: None,
55 completed: None,
56 failed: None,
57 };
58
59 Ok(Json(run))
60}
61
62pub async fn get_run(Path(_id): Path<Uuid>) -> Result<Json<TestRun>, StatusCode> {
63 Err(StatusCode::NOT_FOUND)
65}
66
67pub async fn delete_run(Path(_id): Path<Uuid>) -> Result<StatusCode, StatusCode> {
68 Ok(StatusCode::NO_CONTENT)
70}
71
72pub async fn k8s_health() -> Json<serde_json::Value> {
73 match KubernetesClient::new().await {
75 Ok(client) => match client.health_check().await {
76 Ok(is_healthy) => Json(serde_json::json!({
77 "kubernetes_connected": is_healthy,
78 "timestamp": chrono::Utc::now().to_rfc3339()
79 })),
80 Err(_) => Json(serde_json::json!({
81 "kubernetes_connected": false,
82 "timestamp": chrono::Utc::now().to_rfc3339(),
83 "error": "Kubernetes health check failed"
84 })),
85 },
86 Err(_) => Json(serde_json::json!({
87 "kubernetes_connected": false,
88 "timestamp": chrono::Utc::now().to_rfc3339(),
89 "error": "Could not create Kubernetes client"
90 })),
91 }
92}
93
94pub async fn get_job_logs(Path(job_name): Path<String>) -> Json<serde_json::Value> {
95 match KubernetesClient::new().await {
97 Ok(client) => match client.get_job_logs(&job_name).await {
98 Ok(job_logs) => Json(serde_json::json!({
99 "job_name": job_logs.job_name,
100 "pod_name": job_logs.pod_name,
101 "logs": job_logs.logs,
102 "timestamp": job_logs.timestamp.to_rfc3339(),
103 "status": job_logs.status
104 })),
105 Err(e) => Json(serde_json::json!({
106 "job_name": job_name,
107 "error": format!("Failed to get job logs: {}", e),
108 "timestamp": chrono::Utc::now().to_rfc3339(),
109 "status": "error"
110 })),
111 },
112 Err(_) => Json(serde_json::json!({
113 "job_name": job_name,
114 "error": "Kubernetes client unavailable",
115 "timestamp": chrono::Utc::now().to_rfc3339(),
116 "status": "error"
117 })),
118 }
119}
120
121pub async fn get_job_status(Path(job_name): Path<String>) -> Json<serde_json::Value> {
122 match KubernetesClient::new().await {
124 Ok(client) => match client.get_job_status(&job_name).await {
125 Ok(status) => Json(serde_json::json!({
126 "job_name": job_name,
127 "status": status,
128 "timestamp": chrono::Utc::now().to_rfc3339()
129 })),
130 Err(e) => Json(serde_json::json!({
131 "job_name": job_name,
132 "status": "error",
133 "error": format!("Failed to get job status: {}", e),
134 "timestamp": chrono::Utc::now().to_rfc3339()
135 })),
136 },
137 Err(_) => Json(serde_json::json!({
138 "job_name": job_name,
139 "status": "error",
140 "error": "Kubernetes client unavailable",
141 "timestamp": chrono::Utc::now().to_rfc3339()
142 })),
143 }
144}
145
146pub async fn delete_job(Path(job_name): Path<String>) -> Json<serde_json::Value> {
147 match KubernetesClient::new().await {
149 Ok(client) => match client.delete_job(&job_name).await {
150 Ok(_) => Json(serde_json::json!({
151 "message": format!("Job {} deleted successfully", job_name),
152 "timestamp": chrono::Utc::now().to_rfc3339()
153 })),
154 Err(e) => Json(serde_json::json!({
155 "error": format!("Failed to delete job {}: {}", job_name, e),
156 "timestamp": chrono::Utc::now().to_rfc3339()
157 })),
158 },
159 Err(_) => Json(serde_json::json!({
160 "error": format!("Kubernetes client unavailable - cannot delete job {}", job_name),
161 "timestamp": chrono::Utc::now().to_rfc3339()
162 })),
163 }
164}
165
166pub async fn get_definitions() -> Result<Json<Vec<serde_json::Value>>, StatusCode> {
167 Ok(Json(vec![]))
169}
170
171pub async fn get_executors() -> Result<Json<Vec<serde_json::Value>>, StatusCode> {
172 Ok(Json(vec![]))
174}
175
176pub async fn get_suites() -> Result<Json<Vec<serde_json::Value>>, StatusCode> {
177 Ok(Json(vec![]))
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use axum::Json as JsonBody;
185
186 #[tokio::test]
187 async fn test_health_check() {
188 let response = health_check().await;
189 assert_eq!(response.0.status, "healthy");
190 assert!(!response.0.timestamp.is_empty());
191 }
192
193 #[tokio::test]
194 async fn test_get_runs() {
195 let result = get_runs().await;
196 assert!(result.is_ok());
197 let runs = result.unwrap().0;
198 assert_eq!(runs.len(), 0);
199 }
200
201 #[tokio::test]
202 async fn test_create_run() {
203 let request = CreateRunRequest {
204 name: "Test Run".to_string(),
205 image: "test:latest".to_string(),
206 commands: vec!["echo".to_string(), "hello".to_string()],
207 };
208
209 let result = create_run(JsonBody(request)).await;
210 assert!(result.is_ok());
211
212 let run = result.unwrap().0;
213 assert_eq!(run.name, "Test Run");
214 assert_eq!(run.image, "test:latest");
215 assert_eq!(run.status, "pending");
216 assert_eq!(run.commands.len(), 2);
217 }
218
219 #[tokio::test]
220 async fn test_get_run() {
221 let id = Uuid::new_v4();
222 let result = get_run(Path(id)).await;
223 assert!(result.is_err());
224 assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
225 }
226
227 #[tokio::test]
228 async fn test_delete_run() {
229 let id = Uuid::new_v4();
230 let result = delete_run(Path(id)).await;
231 assert!(result.is_ok());
232 assert_eq!(result.unwrap(), StatusCode::NO_CONTENT);
233 }
234
235 #[tokio::test]
236 async fn test_k8s_health() {
237 let response = k8s_health().await;
238 let value = response.0;
239 assert_eq!(value["kubernetes_connected"], false);
241 assert!(value["timestamp"].is_string());
242 assert!(value["error"].is_string());
243 }
244
245 #[tokio::test]
246 async fn test_get_job_logs() {
247 let job_name = "test-job".to_string();
248 let response = get_job_logs(Path(job_name.clone())).await;
249 let value = response.0;
250 assert_eq!(value["job_name"], job_name);
251 assert_eq!(value["status"], "error");
253 assert!(value["error"].is_string());
254 assert!(value["timestamp"].is_string());
255 }
256
257 #[tokio::test]
258 async fn test_get_job_status() {
259 let job_name = "test-job".to_string();
260 let response = get_job_status(Path(job_name.clone())).await;
261 let value = response.0;
262 assert_eq!(value["job_name"], job_name);
263 assert_eq!(value["status"], "error");
265 assert!(value["error"].is_string());
266 assert!(value["timestamp"].is_string());
267 }
268
269 #[tokio::test]
270 async fn test_delete_job() {
271 let job_name = "test-job".to_string();
272 let response = delete_job(Path(job_name.clone())).await;
273 let value = response.0;
274 assert!(value["error"].is_string());
276 assert!(value["timestamp"].is_string());
277 let error_msg = value["error"].as_str().unwrap();
278 assert!(error_msg.contains("Kubernetes client unavailable"));
279 }
280}