Files
origin_client/rust/tests/client.rs
T
warkanumandClaude Sonnet 5 2590e404f5
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
feat: initial Go/JS/Rust Origin check-in client SDKs
Standalone clients that resolve a machine unique ID (OS machine ID first,
falling back to a generated UUID persisted to disk), detect hostname/IP/
container/orchestrator, and register+re-ping origin.warky.dev's public
service-checkin endpoint on a daily interval. Ported from icy2's internal
origincheckin package but extended to the full check-in contract and made
dependency-light for external reuse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 15:25:30 +02:00

125 lines
3.8 KiB
Rust

use std::sync::Arc;
use std::time::Duration;
use origin_client::{Client, Config, FnLogger};
use tokio_util::sync::CancellationToken;
fn test_config(url: String) -> Config {
Config {
url: Some(url),
service_key: "test-key".to_string(),
type_: "icy2".to_string(),
version: "v1.0.0".to_string(),
name: "icy2-test".to_string(),
site: Some("Test Site".to_string()),
environment: Some("test".to_string()),
install_id: Some("test-install-id".to_string()),
..Default::default()
}
}
#[tokio::test]
async fn new_requires_fields() {
assert!(Client::new(Config::default()).is_err());
}
#[tokio::test]
async fn checkin_once_success() {
let mut server = mockito::Server::new_async().await;
let mock = server
.mock("POST", "/")
.match_header("x-origin-service-key", "test-key")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
r#"{"success":true,"service_instance_id":42,"unique_install_id":"test-install-id","status":"provisioning","pinged_at":"2026-08-01T00:00:00Z"}"#,
)
.create_async()
.await;
let client = Client::new(test_config(server.url())).unwrap();
let resp = client.checkin_once().await.unwrap();
mock.assert_async().await;
assert!(resp.success);
assert_eq!(resp.service_instance_id, 42);
assert_eq!(resp.unique_install_id, "test-install-id");
}
#[tokio::test]
async fn checkin_once_server_error_returns_error() {
let mut server = mockito::Server::new_async().await;
server
.mock("POST", "/")
.with_status(500)
.create_async()
.await;
let client = Client::new(test_config(server.url())).unwrap();
assert!(client.checkin_once().await.is_err());
}
#[tokio::test]
async fn checkin_once_unreachable_does_not_panic() {
let client = Client::new(test_config("http://127.0.0.1:1".to_string())).unwrap();
let result = client.checkin_once().await;
assert!(result.is_err());
}
#[tokio::test]
async fn checkin_once_logs_on_failure() {
let mut server = mockito::Server::new_async().await;
server
.mock("POST", "/")
.with_status(500)
.create_async()
.await;
let warned = Arc::new(std::sync::atomic::AtomicBool::new(false));
let warned_clone = warned.clone();
let mut cfg = test_config(server.url());
cfg.logger = Arc::new(FnLogger(
move |_msg: &str, _err: Option<&(dyn std::error::Error + Send + Sync)>| {
warned_clone.store(true, std::sync::atomic::Ordering::SeqCst);
},
));
let client = Client::new(cfg).unwrap();
let _ = client.checkin_once().await;
assert!(warned.load(std::sync::atomic::Ordering::SeqCst));
}
#[tokio::test]
async fn run_checks_in_immediately_then_stops_on_cancel() {
let mut server = mockito::Server::new_async().await;
let mock = server
.mock("POST", "/")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
r#"{"success":true,"service_instance_id":1,"unique_install_id":"x","status":"provisioning","pinged_at":"2026-08-01T00:00:00Z"}"#,
)
.expect(1)
.create_async()
.await;
let client = Client::new(test_config(server.url())).unwrap();
let cancel = CancellationToken::new();
let cancel_clone = cancel.clone();
let handle = tokio::spawn(async move {
client.run(cancel_clone).await;
});
// The immediate check-in happens synchronously at the start of run();
// give the spawned task a moment to execute it, then cancel before the
// (24h) interval could ever fire again.
tokio::time::sleep(Duration::from_millis(50)).await;
cancel.cancel();
handle.await.unwrap();
mock.assert_async().await;
}