Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
172 lines
5.8 KiB
Rust
172 lines
5.8 KiB
Rust
//! State-machine tests for `DaytonaBackend::acquire`.
|
|
//!
|
|
//! Each test stands up a wiremock server, configures the responses Daytona
|
|
//! would emit for one of the resume-or-create branches, and asserts that
|
|
//! the right HTTP calls were made.
|
|
|
|
use kei_backend_daytona::{
|
|
Backend, CreateSandboxSpec, DaytonaBackend, DaytonaClient, Resources, Sandbox, SandboxState,
|
|
};
|
|
use serde_json::json;
|
|
use wiremock::matchers::{method, path};
|
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
|
|
const TASK_ID: &str = "task-abc";
|
|
|
|
fn sandbox_json(state: &str) -> serde_json::Value {
|
|
json!({
|
|
"id": "sb-id-1",
|
|
"name": format!("kei-{}", TASK_ID),
|
|
"state": state,
|
|
"image": "ubuntu:24.04",
|
|
"resources": { "cpu": 1, "memory": 5, "disk": 10 },
|
|
"labels": { "kei_task_id": TASK_ID }
|
|
})
|
|
}
|
|
|
|
async fn build(server: &MockServer) -> DaytonaBackend {
|
|
let client = DaytonaClient::new("test-key", server.uri()).expect("build client");
|
|
DaytonaBackend::new(client, "ubuntu:24.04")
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn acquire_running_sandbox_skips_start() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(sandbox_json("running")))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
|
|
let handle = backend.acquire(TASK_ID).await.expect("acquire");
|
|
assert_eq!(handle.name, format!("kei-{}", TASK_ID));
|
|
// No /start mock registered → if backend called it, the test fails on
|
|
// the `expect(1)` GET assertion or on a 404 from wiremock.
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn acquire_hibernated_sandbox_calls_start() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(sandbox_json("hibernated")))
|
|
.mount(&server)
|
|
.await;
|
|
Mock::given(method("POST"))
|
|
.and(path(format!("/sandbox/kei-{}/start", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(204))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
|
|
backend.acquire(TASK_ID).await.expect("acquire");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn acquire_stopped_sandbox_calls_start() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(sandbox_json("stopped")))
|
|
.mount(&server)
|
|
.await;
|
|
Mock::given(method("POST"))
|
|
.and(path(format!("/sandbox/kei-{}/start", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(204))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
backend.acquire(TASK_ID).await.expect("acquire");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn acquire_missing_sandbox_creates_new() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(404).set_body_string("not found"))
|
|
.mount(&server)
|
|
.await;
|
|
Mock::given(method("POST"))
|
|
.and(path("/sandbox"))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(sandbox_json("running")))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
|
|
let handle = backend.acquire(TASK_ID).await.expect("acquire");
|
|
assert!(handle.name.contains(TASK_ID));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn acquire_error_state_fails() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("GET"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(200).set_body_json(sandbox_json("error")))
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
let err = backend.acquire(TASK_ID).await.expect_err("should fail");
|
|
let msg = format!("{err}");
|
|
assert!(msg.contains("Error state"), "msg={msg}");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn release_persistent_calls_stop() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("POST"))
|
|
.and(path(format!("/sandbox/kei-{}/stop", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(204))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
let handle = kei_backend_daytona::SandboxHandle {
|
|
name: format!("kei-{}", TASK_ID),
|
|
image: "ubuntu:24.04".into(),
|
|
};
|
|
backend.release(handle, true).await.expect("release");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn release_ephemeral_calls_delete() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("DELETE"))
|
|
.and(path(format!("/sandbox/kei-{}", TASK_ID)))
|
|
.respond_with(ResponseTemplate::new(204))
|
|
.expect(1)
|
|
.mount(&server)
|
|
.await;
|
|
let backend = build(&server).await;
|
|
let handle = kei_backend_daytona::SandboxHandle {
|
|
name: format!("kei-{}", TASK_ID),
|
|
image: "ubuntu:24.04".into(),
|
|
};
|
|
backend.release(handle, false).await.expect("release");
|
|
}
|
|
|
|
#[test]
|
|
fn create_spec_builder_sets_label_and_persistent() {
|
|
let spec = CreateSandboxSpec::new("ubuntu:24.04", "kei-x")
|
|
.with_resources(Resources { cpu: 2, memory: 8, disk: 10 })
|
|
.with_label("kei_task_id", "x")
|
|
.with_persistent();
|
|
assert_eq!(spec.auto_stop_interval, 0);
|
|
assert_eq!(spec.resources.cpu, 2);
|
|
assert_eq!(spec.labels.get("kei_task_id"), Some(&"x".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn sandbox_state_deserializes_unknown_as_unknown() {
|
|
let s: Sandbox = serde_json::from_value(json!({
|
|
"id": "x", "name": "y", "state": "future-value-we-do-not-know",
|
|
}))
|
|
.expect("deserialize");
|
|
assert_eq!(s.state, SandboxState::Unknown);
|
|
}
|