mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
test: add integration tests for API endpoints
Co-authored-by: aider (gemini/gemini-2.5-pro-preview-05-06) <aider@aider.chat>
This commit is contained in:
parent
aa1973603e
commit
ac08ffb54f
1 changed files with 172 additions and 2 deletions
174
src/api.rs
174
src/api.rs
|
|
@ -11,7 +11,7 @@ use crate::sync_logic::LtcState;
|
||||||
use crate::ui;
|
use crate::ui;
|
||||||
|
|
||||||
// Data structure for the main status response
|
// Data structure for the main status response
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ApiStatus {
|
struct ApiStatus {
|
||||||
ltc_status: String,
|
ltc_status: String,
|
||||||
ltc_timecode: String,
|
ltc_timecode: String,
|
||||||
|
|
@ -104,7 +104,7 @@ async fn manual_sync(data: web::Data<AppState>) -> impl Responder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ConfigResponse {
|
struct ConfigResponse {
|
||||||
hardware_offset_ms: i64,
|
hardware_offset_ms: i64,
|
||||||
}
|
}
|
||||||
|
|
@ -163,3 +163,173 @@ pub async fn start_api_server(
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::sync_logic::LtcFrame;
|
||||||
|
use actix_web::{test, App};
|
||||||
|
use chrono::Utc;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
// Helper to create a default LtcState for tests
|
||||||
|
fn get_test_state() -> LtcState {
|
||||||
|
LtcState {
|
||||||
|
latest: Some(LtcFrame {
|
||||||
|
status: "LOCK".to_string(),
|
||||||
|
hours: 1,
|
||||||
|
minutes: 2,
|
||||||
|
seconds: 3,
|
||||||
|
frames: 4,
|
||||||
|
frame_rate: 25.0,
|
||||||
|
timestamp: Utc::now(),
|
||||||
|
}),
|
||||||
|
lock_count: 10,
|
||||||
|
free_count: 1,
|
||||||
|
offset_history: VecDeque::from(vec![1, 2, 3]),
|
||||||
|
clock_delta_history: VecDeque::from(vec![4, 5, 6]),
|
||||||
|
last_match_status: "IN SYNC".to_string(),
|
||||||
|
last_match_check: Utc::now().timestamp(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_status() {
|
||||||
|
let ltc_state = Arc::new(Mutex::new(get_test_state()));
|
||||||
|
let hw_offset = Arc::new(Mutex::new(10i64));
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
ltc_state: ltc_state.clone(),
|
||||||
|
hw_offset: hw_offset.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(get_status),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::get().uri("/api/status").to_request();
|
||||||
|
let resp: ApiStatus = test::call_and_read_body_json(&app, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.ltc_status, "LOCK");
|
||||||
|
assert_eq!(resp.ltc_timecode, "01:02:03:04");
|
||||||
|
assert_eq!(resp.frame_rate, "25.00fps");
|
||||||
|
assert_eq!(resp.hardware_offset_ms, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_config() {
|
||||||
|
let ltc_state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let hw_offset = Arc::new(Mutex::new(25i64));
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
ltc_state: ltc_state.clone(),
|
||||||
|
hw_offset: hw_offset.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(get_config),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::get().uri("/api/config").to_request();
|
||||||
|
let resp: ConfigResponse = test::call_and_read_body_json(&app, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.hardware_offset_ms, 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_update_config() {
|
||||||
|
let ltc_state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let hw_offset = Arc::new(Mutex::new(0i64));
|
||||||
|
let config_path = "config.json";
|
||||||
|
|
||||||
|
// This test has the side effect of writing to `config.json`.
|
||||||
|
// We ensure it's cleaned up after.
|
||||||
|
let _ = fs::remove_file(config_path);
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
ltc_state: ltc_state.clone(),
|
||||||
|
hw_offset: hw_offset.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(update_config),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::post()
|
||||||
|
.uri("/api/config")
|
||||||
|
.set_json(&serde_json::json!({ "hardware_offset_ms": 55 }))
|
||||||
|
.to_request();
|
||||||
|
|
||||||
|
let resp: Config = test::call_and_read_body_json(&app, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.hardware_offset_ms, 55);
|
||||||
|
assert_eq!(*hw_offset.lock().unwrap(), 55);
|
||||||
|
|
||||||
|
// Test that the file was written
|
||||||
|
assert!(fs::metadata(config_path).is_ok());
|
||||||
|
let contents = fs::read_to_string(config_path).unwrap();
|
||||||
|
assert!(contents.contains("\"hardware_offset_ms\": 55"));
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
let _ = fs::remove_file(config_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_manual_sync_no_ltc() {
|
||||||
|
// State with no LTC frame
|
||||||
|
let ltc_state = Arc::new(Mutex::new(LtcState::new()));
|
||||||
|
let hw_offset = Arc::new(Mutex::new(0i64));
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
ltc_state: ltc_state.clone(),
|
||||||
|
hw_offset: hw_offset.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(manual_sync),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::post().uri("/api/sync").to_request();
|
||||||
|
let resp = test::call_service(&app, req).await;
|
||||||
|
|
||||||
|
assert_eq!(resp.status(), 400); // Bad Request
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_manual_sync_fails() {
|
||||||
|
// State with an LTC frame, but sync command will fail in test env
|
||||||
|
let ltc_state = Arc::new(Mutex::new(get_test_state()));
|
||||||
|
let hw_offset = Arc::new(Mutex::new(0i64));
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
ltc_state: ltc_state.clone(),
|
||||||
|
hw_offset: hw_offset.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = test::init_service(
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.service(manual_sync),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let req = test::TestRequest::post().uri("/api/sync").to_request();
|
||||||
|
let resp = test::call_service(&app, req).await;
|
||||||
|
|
||||||
|
// Expecting failure because `sudo date` won't work here.
|
||||||
|
assert_eq!(resp.status(), 500); // Internal Server Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue