Private
Public Access
feat: initial Go/JS/Rust Origin check-in client SDKs
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
CI / rust (push) Successful in 3m6s
CI / go (push) Successful in 33s
CI / js (push) Successful in 34s
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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user