Add runnable smoke-test clients for each SDK
CI / js (push) Successful in 18s
CI / go (push) Successful in 36s
CI / rust (push) Successful in 2m3s

Each package gets a small example program that exercises the real
client: with no ORIGIN_SERVICE_KEY set it runs against a local mock
check-in server, otherwise it checks in against ORIGIN_URL/Origin's
public endpoint. Wired into CI (mock mode) as a regression check, and
documented per README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-01 15:38:37 +02:00
co-authored by Claude Sonnet 5
parent 2590e404f5
commit ac3c0f149c
9 changed files with 331 additions and 2 deletions
+3
View File
@@ -20,6 +20,7 @@ jobs:
- run: go vet ./...
- run: test -z "$(gofmt -l .)"
- run: go test ./...
- run: go run ./examples/testclient
js:
runs-on: ubuntu-latest
@@ -37,6 +38,7 @@ jobs:
- run: npm run typecheck
- run: npm run build
- run: npm test
- run: node examples/testclient.mjs
rust:
runs-on: ubuntu-latest
@@ -52,3 +54,4 @@ jobs:
- run: cargo clippy --all-targets -- -D warnings
- run: cargo build
- run: cargo test
- run: cargo run --example testclient
+11
View File
@@ -43,3 +43,14 @@ check-in and observe the error/response.
`Hostname()`, `OutboundIP()`, `ContainerID()`, `Orchestrator()`, and
`ResolveMachineID()` are exported standalone for callers that want the
detection logic without the HTTP client.
## Manual smoke test
```bash
go run ./examples/testclient # single check-in, local mock server
go run ./examples/testclient -watch # loops via Run() until Ctrl-C
```
Set `ORIGIN_SERVICE_KEY` (and optionally `ORIGIN_URL`, `ORIGIN_TYPE`,
`ORIGIN_VERSION`, `ORIGIN_NAME`, `ORIGIN_SITE`, `ORIGIN_ENVIRONMENT`,
`ORIGIN_INTERVAL_HOURS`) to check in against a real endpoint instead.
+98
View File
@@ -0,0 +1,98 @@
// Command testclient is a runnable smoke test for the Go originclient SDK.
//
// Without ORIGIN_SERVICE_KEY set, it spins up a local mock check-in server
// and exercises the client against it — no network, no credentials needed.
// With ORIGIN_SERVICE_KEY set, it performs a real check-in against
// ORIGIN_URL (or Origin's public endpoint) so the SDK can be smoke-tested
// end-to-end against a live server.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"os/signal"
"strconv"
"syscall"
originclient "git.warky.dev/wdevs/origin_client/go"
)
func main() {
watch := flag.Bool("watch", false, "run continuously via Run() instead of a single check-in")
flag.Parse()
cfg := originclient.Config{
URL: os.Getenv("ORIGIN_URL"),
ServiceKey: os.Getenv("ORIGIN_SERVICE_KEY"),
Type: envOr("ORIGIN_TYPE", "icy2-testclient"),
Version: envOr("ORIGIN_VERSION", "0.0.0-test"),
Name: envOr("ORIGIN_NAME", "icy2-testclient-local"),
Site: os.Getenv("ORIGIN_SITE"),
Environment: os.Getenv("ORIGIN_ENVIRONMENT"),
Logger: originclient.LoggerFunc(func(msg string, err error) { log.Printf("[warn] %s: %v", msg, err) }),
}
if cfg.ServiceKey == "" {
log.Println("ORIGIN_SERVICE_KEY not set — using a local mock check-in server")
server := httptest.NewServer(http.HandlerFunc(mockHandler))
defer server.Close()
cfg.URL = server.URL
cfg.ServiceKey = "test-key"
cfg.InstallID = "test-install-id"
}
if h := os.Getenv("ORIGIN_INTERVAL_HOURS"); h != "" {
if n, err := strconv.Atoi(h); err == nil {
cfg.IntervalHours = n
}
}
client, err := originclient.New(cfg)
if err != nil {
log.Fatalf("originclient.New: %v", err)
}
if *watch {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
log.Println("watching — Ctrl-C to stop")
client.Run(ctx)
return
}
resp, err := client.CheckinOnce(context.Background())
if err != nil {
log.Fatalf("checkin failed: %v", err)
}
out, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(out))
}
func mockHandler(w http.ResponseWriter, r *http.Request) {
var payload originclient.Payload
_ = json.NewDecoder(r.Body).Decode(&payload)
log.Printf("mock server received check-in: type=%s name=%s install_id=%s key=%s",
payload.Type, payload.Name, payload.UniqueInstallID, r.Header.Get("X-Origin-Service-Key"))
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(originclient.Response{
Success: true,
ServiceInstanceID: 1,
UniqueInstallID: payload.UniqueInstallID,
Status: "provisioning",
PingedAt: "2026-08-01T00:00:00Z",
})
}
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}
+11
View File
@@ -45,3 +45,14 @@ check-in and observe the result/error.
`hostname()`, `outboundIp()`, `containerId()`, `orchestrator()`, and
`resolveMachineId()` are exported standalone for callers that want the
detection logic without the HTTP client.
## Manual smoke test
```bash
npm run example # single check-in, local mock server
node examples/testclient.mjs --watch # loops via start() until Ctrl-C
```
Set `ORIGIN_SERVICE_KEY` (and optionally `ORIGIN_URL`, `ORIGIN_TYPE`,
`ORIGIN_VERSION`, `ORIGIN_NAME`, `ORIGIN_SITE`, `ORIGIN_ENVIRONMENT`,
`ORIGIN_INTERVAL_HOURS`) to check in against a real endpoint instead.
+103
View File
@@ -0,0 +1,103 @@
// Runnable smoke test for the JS/TS originclient SDK.
//
// Without ORIGIN_SERVICE_KEY set, it spins up a local mock check-in server
// and exercises the client against it — no network, no credentials needed.
// With ORIGIN_SERVICE_KEY set, it performs a real check-in against
// ORIGIN_URL (or Origin's public endpoint) so the SDK can be smoke-tested
// end-to-end against a live server.
//
// Run `npm run build` first (this imports the compiled dist/ output).
// Usage: node examples/testclient.mjs [--watch]
import { createServer } from "node:http";
import { OriginClient } from "../dist/index.js";
function envOr(key, def) {
return process.env[key] || def;
}
async function startMockServer() {
return new Promise((resolve) => {
const server = createServer((req, res) => {
let raw = "";
req.on("data", (chunk) => {
raw += chunk;
});
req.on("end", () => {
const payload = JSON.parse(raw || "{}");
console.log(
`mock server received check-in: type=${payload.type} name=${payload.name} ` +
`install_id=${payload.unique_install_id} key=${req.headers["x-origin-service-key"]}`,
);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
success: true,
service_instance_id: 1,
unique_install_id: payload.unique_install_id,
status: "provisioning",
pinged_at: "2026-08-01T00:00:00Z",
}),
);
});
});
server.listen(0, "127.0.0.1", () => {
const address = server.address();
resolve({ server, url: `http://127.0.0.1:${address.port}` });
});
});
}
async function main() {
const watch = process.argv.includes("--watch");
const config = {
url: process.env.ORIGIN_URL,
serviceKey: process.env.ORIGIN_SERVICE_KEY,
type: envOr("ORIGIN_TYPE", "icy2-testclient"),
version: envOr("ORIGIN_VERSION", "0.0.0-test"),
name: envOr("ORIGIN_NAME", "icy2-testclient-local"),
site: process.env.ORIGIN_SITE,
environment: process.env.ORIGIN_ENVIRONMENT,
logger: { warn: (msg, err) => console.warn(`[warn] ${msg}:`, err) },
};
let mockServer;
if (!config.serviceKey) {
console.log("ORIGIN_SERVICE_KEY not set — using a local mock check-in server");
const { server, url } = await startMockServer();
mockServer = server;
config.url = url;
config.serviceKey = "test-key";
config.installId = "test-install-id";
}
if (process.env.ORIGIN_INTERVAL_HOURS) {
config.intervalHours = Number(process.env.ORIGIN_INTERVAL_HOURS);
}
const client = new OriginClient(config);
try {
if (watch) {
console.log("watching — Ctrl-C to stop");
client.start();
await new Promise((resolve) => {
process.on("SIGINT", resolve);
process.on("SIGTERM", resolve);
});
client.stop();
return;
}
const resp = await client.checkinOnce();
console.log(JSON.stringify(resp, null, 2));
} finally {
mockServer?.close();
}
}
main().catch((err) => {
console.error("checkin failed:", err);
process.exitCode = 1;
});
+2 -1
View File
@@ -21,7 +21,8 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"example": "npm run build && node examples/testclient.mjs"
},
"devDependencies": {
"@types/node": "^22.10.0",
+1 -1
View File
@@ -22,4 +22,4 @@ winreg = "0.52"
[dev-dependencies]
mockito = "1"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "signal"] }
+11
View File
@@ -54,6 +54,17 @@ default. `Config` implements `Default`, so use struct-update syntax
`resolve_machine_id()` are exported standalone for callers that want the
detection logic without the HTTP client.
## Manual smoke test
```bash
cargo run --example testclient # single check-in, local mock server
cargo run --example testclient -- --watch # loops via run() until Ctrl-C
```
Set `ORIGIN_SERVICE_KEY` (and optionally `ORIGIN_URL`, `ORIGIN_TYPE`,
`ORIGIN_VERSION`, `ORIGIN_NAME`, `ORIGIN_SITE`, `ORIGIN_ENVIRONMENT`,
`ORIGIN_INTERVAL_HOURS`) to check in against a real endpoint instead.
## Platform notes
macOS/Windows machine-ID detection is compiled but not exercised by CI
+91
View File
@@ -0,0 +1,91 @@
//! Runnable smoke test for the Rust origin-client SDK.
//!
//! Without ORIGIN_SERVICE_KEY set, it spins up a local mock check-in server
//! and exercises the client against it — no network, no credentials needed.
//! With ORIGIN_SERVICE_KEY set, it performs a real check-in against
//! ORIGIN_URL (or Origin's public endpoint) so the SDK can be smoke-tested
//! end-to-end against a live server.
//!
//! Usage: `cargo run --example testclient [-- --watch]`
use std::env;
use std::sync::Arc;
use origin_client::{Client, Config, FnLogger};
use tokio_util::sync::CancellationToken;
fn env_or(key: &str, default: &str) -> String {
env::var(key).unwrap_or_else(|_| default.to_string())
}
#[tokio::main]
async fn main() {
let watch = env::args().any(|a| a == "--watch");
let mut cfg = Config {
url: env::var("ORIGIN_URL").ok(),
service_key: env::var("ORIGIN_SERVICE_KEY").unwrap_or_default(),
type_: env_or("ORIGIN_TYPE", "icy2-testclient"),
version: env_or("ORIGIN_VERSION", "0.0.0-test"),
name: env_or("ORIGIN_NAME", "icy2-testclient-local"),
site: env::var("ORIGIN_SITE").ok(),
environment: env::var("ORIGIN_ENVIRONMENT").ok(),
logger: Arc::new(FnLogger(|msg: &str, err| {
eprintln!("[warn] {msg}: {err:?}");
})),
..Default::default()
};
// Held so the mock server stays alive for the client's lifetime; unused
// in real-endpoint mode.
let mut _mock_server = None;
if cfg.service_key.is_empty() {
println!("ORIGIN_SERVICE_KEY not set — using a local mock check-in server");
let mut server = mockito::Server::new_async().await;
server
.mock("POST", "/")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
r#"{"success":true,"service_instance_id":1,"unique_install_id":"test-install-id","status":"provisioning","pinged_at":"2026-08-01T00:00:00Z"}"#,
)
.create_async()
.await;
cfg.url = Some(server.url());
cfg.service_key = "test-key".to_string();
cfg.install_id = Some("test-install-id".to_string());
_mock_server = Some(server);
}
if let Ok(h) = env::var("ORIGIN_INTERVAL_HOURS") {
if let Ok(n) = h.parse() {
cfg.interval_hours = Some(n);
}
}
let client = Client::new(cfg).expect("origin_client::Config validation failed");
if watch {
println!("watching — Ctrl-C to stop");
let cancel = CancellationToken::new();
let cancel_clone = cancel.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.ok();
cancel_clone.cancel();
});
client.run(cancel).await;
return;
}
match client.checkin_once().await {
Ok(resp) => {
println!("{resp:#?}");
}
Err(err) => {
eprintln!("checkin failed: {err}");
std::process::exit(1);
}
}
}