Spaces:
Sleeping
Sleeping
| //! Unit tests for the retained-job SSE payload contract. | |
| use super::*; | |
| use crate::domain::{Employee, Shift}; | |
| use serde_json::Value; | |
| use solverforge::{ | |
| SolverLifecycleState, SolverSnapshot, SolverStatus, SolverTelemetry, SolverTerminalReason, | |
| }; | |
| fn failed_events_use_stock_payload_fields() { | |
| let payload: Value = serde_json::from_str(&event_payload( | |
| 7, | |
| "failed", | |
| &SolverEventMetadata { | |
| job_id: 7, | |
| event_sequence: 4, | |
| lifecycle_state: SolverLifecycleState::Failed, | |
| terminal_reason: Some(SolverTerminalReason::Failed), | |
| snapshot_revision: Some(3), | |
| current_score: None, | |
| best_score: None, | |
| telemetry: SolverTelemetry::default(), | |
| }, | |
| None, | |
| Some("boom"), | |
| )) | |
| .unwrap(); | |
| assert_eq!(payload["id"], "7"); | |
| assert_eq!(payload["jobId"], "7"); | |
| assert_eq!(payload["eventType"], "failed"); | |
| assert_eq!(payload["lifecycleState"], "FAILED"); | |
| assert_eq!(payload["terminalReason"], "failed"); | |
| assert_eq!(payload["error"], "boom"); | |
| } | |
| fn event_payload_derives_stock_telemetry_fields_from_exact_runtime_telemetry() { | |
| let payload: Value = serde_json::from_str(&event_payload( | |
| 5, | |
| "progress", | |
| &SolverEventMetadata { | |
| job_id: 5, | |
| event_sequence: 2, | |
| lifecycle_state: SolverLifecycleState::Solving, | |
| terminal_reason: None, | |
| snapshot_revision: Some(1), | |
| current_score: Some(HardSoftDecimalScore::ZERO), | |
| best_score: Some(HardSoftDecimalScore::ZERO), | |
| telemetry: SolverTelemetry { | |
| elapsed: std::time::Duration::from_millis(2_500), | |
| step_count: 9, | |
| moves_generated: 300, | |
| moves_evaluated: 200, | |
| moves_accepted: 50, | |
| score_calculations: 80, | |
| generation_time: std::time::Duration::from_millis(400), | |
| evaluation_time: std::time::Duration::from_millis(900), | |
| ..SolverTelemetry::default() | |
| }, | |
| }, | |
| None, | |
| None, | |
| )) | |
| .unwrap(); | |
| assert_eq!(payload["telemetry"]["elapsedMs"], 2500); | |
| assert_eq!(payload["telemetry"]["stepCount"], 9); | |
| assert_eq!(payload["telemetry"]["movesGenerated"], 300); | |
| assert_eq!(payload["telemetry"]["movesEvaluated"], 200); | |
| assert_eq!(payload["telemetry"]["movesAccepted"], 50); | |
| assert_eq!(payload["telemetry"]["scoreCalculations"], 80); | |
| assert_eq!(payload["telemetry"]["generationMs"], 400); | |
| assert_eq!(payload["telemetry"]["evaluationMs"], 900); | |
| assert_eq!(payload["telemetry"]["movesPerSecond"], 80); | |
| assert_eq!(payload["telemetry"]["acceptanceRate"], 0.25); | |
| } | |
| fn snapshot_bootstrap_payload_exposes_live_solution_and_ui_score_fields() { | |
| let mut solution = Plan::new( | |
| vec![Employee::new(0, "Alex").with_skill("Doctor")], | |
| vec![{ | |
| let mut shift = Shift::new( | |
| "shift-1", | |
| chrono::NaiveDate::from_ymd_opt(2024, 1, 1) | |
| .unwrap() | |
| .and_hms_opt(8, 0, 0) | |
| .unwrap(), | |
| chrono::NaiveDate::from_ymd_opt(2024, 1, 1) | |
| .unwrap() | |
| .and_hms_opt(16, 0, 0) | |
| .unwrap(), | |
| "ER", | |
| "Doctor", | |
| ); | |
| shift.employee_idx = Some(0); | |
| shift | |
| }], | |
| ); | |
| solution.score = Some(HardSoftDecimalScore::ZERO); | |
| let status = SolverStatus { | |
| job_id: 11, | |
| lifecycle_state: SolverLifecycleState::Solving, | |
| terminal_reason: None, | |
| checkpoint_available: true, | |
| event_sequence: 9, | |
| latest_snapshot_revision: Some(4), | |
| current_score: None, | |
| best_score: None, | |
| telemetry: SolverTelemetry::default(), | |
| }; | |
| let snapshot = SolverSnapshot { | |
| job_id: 11, | |
| snapshot_revision: 4, | |
| lifecycle_state: SolverLifecycleState::Solving, | |
| terminal_reason: None, | |
| current_score: Some(HardSoftDecimalScore::ZERO), | |
| best_score: Some(HardSoftDecimalScore::ZERO), | |
| telemetry: SolverTelemetry::default(), | |
| solution, | |
| }; | |
| let payload: Value = serde_json::from_str(&snapshot_status_event_payload( | |
| 11, | |
| bootstrap_snapshot_event_type(status.lifecycle_state), | |
| &status, | |
| &snapshot, | |
| )) | |
| .unwrap(); | |
| assert_eq!(payload["eventType"], "best_solution"); | |
| assert_eq!(payload["lifecycleState"], "SOLVING"); | |
| assert_eq!(payload["snapshotRevision"], 4); | |
| assert_eq!(payload["currentScore"], "0hard/0soft"); | |
| assert_eq!(payload["bestScore"], "0hard/0soft"); | |
| assert!(payload["solution"].is_object()); | |
| assert_eq!(payload["solution"]["shifts"][0]["employeeIdx"], 0); | |
| assert_eq!(payload["solution"]["score"], "0hard/0soft"); | |
| } | |