Private
Public Access
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
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:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user