mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-06 08:18:58 +00:00
729 lines
23 KiB
Rust
729 lines
23 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use axum::body::Body;
|
|
use axum::http::{Method, Request, StatusCode, header};
|
|
use fabro_config::{RunEnvironmentLayer, RunLayer};
|
|
use fabro_server::server::build_router;
|
|
use fabro_server::test_support::{
|
|
TestAppStateBuilder, build_test_router, default_test_server_settings, test_auth_mode,
|
|
test_environment_from_storage_dir,
|
|
};
|
|
use serde_json::{Value, json};
|
|
use tower::ServiceExt;
|
|
|
|
use crate::helpers::{api, checked_response, response_json, response_status};
|
|
|
|
fn environment_settings(provider: &str) -> Value {
|
|
json!({
|
|
"provider": provider,
|
|
"image": {
|
|
"docker": if provider == "docker" { json!("alpine:3.20") } else { Value::Null },
|
|
"dockerfile": null
|
|
},
|
|
"resources": {
|
|
"cpu": null,
|
|
"memory": null,
|
|
"disk": null
|
|
},
|
|
"network": {
|
|
"mode": "allow_all",
|
|
"allow": []
|
|
},
|
|
"lifecycle": {
|
|
"preserve": false,
|
|
"stop_on_terminal": true,
|
|
"auto_stop": null
|
|
},
|
|
"labels": {},
|
|
"env": {}
|
|
})
|
|
}
|
|
|
|
fn environment_body(id: &str, provider: &str) -> Value {
|
|
let mut body = environment_settings(provider);
|
|
body["id"] = json!(id);
|
|
body
|
|
}
|
|
|
|
fn environment_app() -> (axum::Router, tempfile::TempDir, PathBuf) {
|
|
let temp_dir = tempfile::tempdir().expect("environment test tempdir should be created");
|
|
let active_config_path = temp_dir.path().join("settings.toml");
|
|
let environment_dir = temp_dir.path().join("environments");
|
|
let vault_path = temp_dir.path().join("secrets.json");
|
|
let state = TestAppStateBuilder::new()
|
|
.active_config_path(active_config_path)
|
|
.vault_path(vault_path)
|
|
.build();
|
|
(build_test_router(state), temp_dir, environment_dir)
|
|
}
|
|
|
|
fn environment_app_with_default_environment(
|
|
environment_id: &str,
|
|
) -> (axum::Router, tempfile::TempDir) {
|
|
let temp_dir = tempfile::tempdir().expect("environment test tempdir should be created");
|
|
let active_config_path = temp_dir.path().join("settings.toml");
|
|
let vault_path = temp_dir.path().join("secrets.json");
|
|
let manifest_run_defaults = RunLayer {
|
|
environment: Some(RunEnvironmentLayer {
|
|
id: Some(environment_id.to_string()),
|
|
..RunEnvironmentLayer::default()
|
|
}),
|
|
..RunLayer::default()
|
|
};
|
|
let state = TestAppStateBuilder::new()
|
|
.runtime_settings(default_test_server_settings(), manifest_run_defaults)
|
|
.active_config_path(active_config_path)
|
|
.vault_path(vault_path)
|
|
.build();
|
|
(build_test_router(state), temp_dir)
|
|
}
|
|
|
|
fn json_request(method: Method, path: &str, body: &Value) -> Request<Body> {
|
|
Request::builder()
|
|
.method(method)
|
|
.uri(api(path))
|
|
.header(header::CONTENT_TYPE, "application/json")
|
|
.body(Body::from(
|
|
serde_json::to_vec(body).expect("environment fixture should serialize"),
|
|
))
|
|
.expect("environment JSON request should build")
|
|
}
|
|
|
|
fn empty_request(method: Method, path: &str) -> Request<Body> {
|
|
Request::builder()
|
|
.method(method)
|
|
.uri(api(path))
|
|
.body(Body::empty())
|
|
.expect("environment request should build")
|
|
}
|
|
|
|
fn request_with_if_match(
|
|
method: Method,
|
|
path: &str,
|
|
revision: &str,
|
|
body: Option<Value>,
|
|
) -> Request<Body> {
|
|
let mut builder = Request::builder()
|
|
.method(method)
|
|
.uri(api(path))
|
|
.header(header::IF_MATCH, revision);
|
|
let body = match body {
|
|
Some(value) => {
|
|
builder = builder.header(header::CONTENT_TYPE, "application/json");
|
|
Body::from(serde_json::to_vec(&value).expect("environment fixture should serialize"))
|
|
}
|
|
None => Body::empty(),
|
|
};
|
|
builder
|
|
.body(body)
|
|
.expect("environment If-Match request should build")
|
|
}
|
|
|
|
async fn create_environment(app: &axum::Router, id: &str, provider: &str) -> Value {
|
|
create_environment_with_body(app, &environment_body(id, provider)).await
|
|
}
|
|
|
|
async fn create_environment_with_body(app: &axum::Router, body: &Value) -> Value {
|
|
let response = app
|
|
.clone()
|
|
.oneshot(json_request(Method::POST, "/environments", body))
|
|
.await
|
|
.expect("create environment should respond");
|
|
response_json(response, StatusCode::CREATED, "POST /api/v1/environments").await
|
|
}
|
|
|
|
fn revision_from(body: &Value) -> &str {
|
|
body["revision"]
|
|
.as_str()
|
|
.expect("environment response should include a revision")
|
|
}
|
|
|
|
async fn persisted_environment(
|
|
temp_dir: &tempfile::TempDir,
|
|
id: &str,
|
|
) -> Option<fabro_environment::Environment> {
|
|
test_environment_from_storage_dir(temp_dir.path(), id)
|
|
.await
|
|
.expect("environment store should load from test storage")
|
|
}
|
|
|
|
async fn system_info(app: &axum::Router) -> Value {
|
|
let response = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/system/info"))
|
|
.await
|
|
.expect("system info should respond");
|
|
response_json(response, StatusCode::OK, "GET /api/v1/system/info").await
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn list_environments_returns_seeded_catalog_sorted_by_id() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
|
|
let response = app
|
|
.oneshot(empty_request(Method::GET, "/environments"))
|
|
.await
|
|
.expect("list environments should respond");
|
|
let body = response_json(response, StatusCode::OK, "GET /api/v1/environments").await;
|
|
|
|
assert_eq!(body["meta"]["total"], 2);
|
|
assert_eq!(
|
|
body["data"]
|
|
.as_array()
|
|
.expect("environment list data should be an array")
|
|
.iter()
|
|
.map(|environment| environment["id"]
|
|
.as_str()
|
|
.expect("environment should have id"))
|
|
.collect::<Vec<_>>(),
|
|
vec!["default", "local"]
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn create_environment_persists_to_sqlite_and_is_visible() {
|
|
let (app, temp_dir, environment_dir) = environment_app();
|
|
let mut body = environment_body("custom-env", "docker");
|
|
body["cwd"] = json!("/workspace/custom");
|
|
|
|
let created = create_environment_with_body(&app, &body).await;
|
|
|
|
assert_eq!(created["id"], "custom-env");
|
|
assert_eq!(created["provider"], "docker");
|
|
assert_eq!(created["cwd"], "/workspace/custom");
|
|
assert!(!environment_dir.join("custom-env.toml").exists());
|
|
|
|
let retrieved = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/environments/custom-env"))
|
|
.await
|
|
.expect("get environment should respond");
|
|
let retrieved = response_json(
|
|
retrieved,
|
|
StatusCode::OK,
|
|
"GET /api/v1/environments/custom-env",
|
|
)
|
|
.await;
|
|
assert_eq!(retrieved["id"], "custom-env");
|
|
assert_eq!(retrieved["cwd"], "/workspace/custom");
|
|
|
|
let list = app
|
|
.oneshot(empty_request(Method::GET, "/environments"))
|
|
.await
|
|
.expect("list environments should respond");
|
|
let list = response_json(list, StatusCode::OK, "GET /api/v1/environments").await;
|
|
assert_eq!(list["meta"]["total"], 3);
|
|
assert!(
|
|
list["data"]
|
|
.as_array()
|
|
.expect("environment list data should be an array")
|
|
.iter()
|
|
.any(|environment| environment["id"] == "custom-env")
|
|
);
|
|
|
|
let persisted = persisted_environment(&temp_dir, "custom-env")
|
|
.await
|
|
.expect("custom environment should persist to SQLite");
|
|
assert_eq!(persisted.settings.provider.to_string(), "docker");
|
|
assert_eq!(persisted.settings.cwd.as_deref(), Some("/workspace/custom"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn get_environment_returns_current_etag() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
let created = create_environment(&app, "etag-env", "local").await;
|
|
let revision = revision_from(&created);
|
|
|
|
let response = app
|
|
.oneshot(empty_request(Method::GET, "/environments/etag-env"))
|
|
.await
|
|
.expect("get environment should respond");
|
|
let response = checked_response(
|
|
response,
|
|
StatusCode::OK,
|
|
"GET /api/v1/environments/etag-env",
|
|
)
|
|
.await;
|
|
|
|
assert_eq!(
|
|
response
|
|
.headers()
|
|
.get(header::ETAG)
|
|
.expect("GET environment should include ETag"),
|
|
&format!("\"{revision}\"")
|
|
);
|
|
let body = crate::helpers::body_json(response.into_body()).await;
|
|
assert_eq!(body["revision"], revision);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn replace_environment_updates_sqlite_and_returns_new_etag() {
|
|
let (app, temp_dir, environment_dir) = environment_app();
|
|
let created = create_environment(&app, "replace-env", "docker").await;
|
|
let revision = revision_from(&created);
|
|
let mut replacement = environment_settings("local");
|
|
replacement["labels"] = json!({ "tier": "dev" });
|
|
replacement["cwd"] = json!("/srv/fabro/local");
|
|
|
|
let response = app
|
|
.oneshot(request_with_if_match(
|
|
Method::PUT,
|
|
"/environments/replace-env",
|
|
revision,
|
|
Some(replacement),
|
|
))
|
|
.await
|
|
.expect("replace environment should respond");
|
|
let response = checked_response(
|
|
response,
|
|
StatusCode::OK,
|
|
"PUT /api/v1/environments/replace-env",
|
|
)
|
|
.await;
|
|
let etag = response
|
|
.headers()
|
|
.get(header::ETAG)
|
|
.expect("PUT environment should include ETag")
|
|
.to_str()
|
|
.expect("ETag should be ASCII")
|
|
.to_string();
|
|
let body = crate::helpers::body_json(response.into_body()).await;
|
|
|
|
assert_eq!(body["provider"], "local");
|
|
assert_eq!(body["labels"]["tier"], "dev");
|
|
assert_eq!(body["cwd"], "/srv/fabro/local");
|
|
assert_ne!(body["revision"], revision);
|
|
assert_eq!(etag, format!("\"{}\"", revision_from(&body)));
|
|
assert!(!environment_dir.join("replace-env.toml").exists());
|
|
let persisted = persisted_environment(&temp_dir, "replace-env")
|
|
.await
|
|
.expect("replacement should persist to SQLite");
|
|
assert_eq!(
|
|
persisted.settings.labels.get("tier").map(String::as_str),
|
|
Some("dev")
|
|
);
|
|
assert_eq!(persisted.settings.cwd.as_deref(), Some("/srv/fabro/local"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn replace_and_delete_environment_require_if_match() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
create_environment(&app, "match-env", "local").await;
|
|
|
|
let replace_response = app
|
|
.clone()
|
|
.oneshot(json_request(
|
|
Method::PUT,
|
|
"/environments/match-env",
|
|
&environment_settings("docker"),
|
|
))
|
|
.await
|
|
.expect("replace without If-Match should respond");
|
|
response_status(
|
|
replace_response,
|
|
StatusCode::PRECONDITION_REQUIRED,
|
|
"PUT /api/v1/environments/match-env without If-Match",
|
|
)
|
|
.await;
|
|
|
|
let delete_response = app
|
|
.oneshot(empty_request(Method::DELETE, "/environments/match-env"))
|
|
.await
|
|
.expect("delete without If-Match should respond");
|
|
response_status(
|
|
delete_response,
|
|
StatusCode::PRECONDITION_REQUIRED,
|
|
"DELETE /api/v1/environments/match-env without If-Match",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn stale_environment_replace_and_delete_return_conflict() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
let created = create_environment(&app, "stale-env", "docker").await;
|
|
let stale_revision = revision_from(&created).to_string();
|
|
|
|
let replaced = app
|
|
.clone()
|
|
.oneshot(request_with_if_match(
|
|
Method::PUT,
|
|
"/environments/stale-env",
|
|
&stale_revision,
|
|
Some(environment_settings("local")),
|
|
))
|
|
.await
|
|
.expect("first replace should respond");
|
|
response_status(
|
|
replaced,
|
|
StatusCode::OK,
|
|
"PUT /api/v1/environments/stale-env first replace",
|
|
)
|
|
.await;
|
|
|
|
let stale_replace = app
|
|
.clone()
|
|
.oneshot(request_with_if_match(
|
|
Method::PUT,
|
|
"/environments/stale-env",
|
|
&stale_revision,
|
|
Some(environment_settings("docker")),
|
|
))
|
|
.await
|
|
.expect("stale replace should respond");
|
|
response_status(
|
|
stale_replace,
|
|
StatusCode::CONFLICT,
|
|
"PUT /api/v1/environments/stale-env stale",
|
|
)
|
|
.await;
|
|
|
|
let stale_delete = app
|
|
.oneshot(request_with_if_match(
|
|
Method::DELETE,
|
|
"/environments/stale-env",
|
|
&stale_revision,
|
|
None,
|
|
))
|
|
.await
|
|
.expect("stale delete should respond");
|
|
response_status(
|
|
stale_delete,
|
|
StatusCode::CONFLICT,
|
|
"DELETE /api/v1/environments/stale-env stale",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn duplicate_environment_create_returns_conflict() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
create_environment(&app, "duplicate-env", "local").await;
|
|
|
|
let response = app
|
|
.oneshot(json_request(
|
|
Method::POST,
|
|
"/environments",
|
|
&environment_body("duplicate-env", "docker"),
|
|
))
|
|
.await
|
|
.expect("duplicate create should respond");
|
|
|
|
response_status(
|
|
response,
|
|
StatusCode::CONFLICT,
|
|
"POST /api/v1/environments duplicate",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reserved_local_environment_cannot_be_created_or_modified() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
|
|
// `local` is reserved: creating it is rejected with a conflict.
|
|
let created = app
|
|
.clone()
|
|
.oneshot(json_request(
|
|
Method::POST,
|
|
"/environments",
|
|
&environment_body("local", "local"),
|
|
))
|
|
.await
|
|
.expect("reserved create should respond");
|
|
response_status(
|
|
created,
|
|
StatusCode::CONFLICT,
|
|
"POST /api/v1/environments local",
|
|
)
|
|
.await;
|
|
|
|
// It is synthesized in memory (local provider enabled) and readable.
|
|
let local = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/environments/local"))
|
|
.await
|
|
.expect("get local should respond");
|
|
let local = response_json(local, StatusCode::OK, "GET /api/v1/environments/local").await;
|
|
let revision = revision_from(&local);
|
|
|
|
// Replace and delete are rejected even with a valid If-Match.
|
|
let replaced = app
|
|
.clone()
|
|
.oneshot(request_with_if_match(
|
|
Method::PUT,
|
|
"/environments/local",
|
|
&format!("\"{revision}\""),
|
|
Some(environment_settings("local")),
|
|
))
|
|
.await
|
|
.expect("reserved replace should respond");
|
|
response_status(
|
|
replaced,
|
|
StatusCode::CONFLICT,
|
|
"PUT /api/v1/environments/local",
|
|
)
|
|
.await;
|
|
|
|
let deleted = app
|
|
.oneshot(request_with_if_match(
|
|
Method::DELETE,
|
|
"/environments/local",
|
|
&format!("\"{revision}\""),
|
|
None,
|
|
))
|
|
.await
|
|
.expect("reserved delete should respond");
|
|
response_status(
|
|
deleted,
|
|
StatusCode::CONFLICT,
|
|
"DELETE /api/v1/environments/local",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_environment_id_and_if_match_return_bad_request() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
|
|
let invalid_id = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/environments/Bad!"))
|
|
.await
|
|
.expect("invalid id request should respond");
|
|
response_status(
|
|
invalid_id,
|
|
StatusCode::BAD_REQUEST,
|
|
"GET /api/v1/environments/Bad!",
|
|
)
|
|
.await;
|
|
|
|
create_environment(&app, "header-env", "local").await;
|
|
let invalid_header = app
|
|
.oneshot(request_with_if_match(
|
|
Method::PUT,
|
|
"/environments/header-env",
|
|
"not-a-revision",
|
|
Some(environment_settings("docker")),
|
|
))
|
|
.await
|
|
.expect("invalid If-Match request should respond");
|
|
response_status(
|
|
invalid_header,
|
|
StatusCode::BAD_REQUEST,
|
|
"PUT /api/v1/environments/header-env invalid If-Match",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn invalid_environment_settings_return_unprocessable_entity() {
|
|
let (app, _temp_dir, _environment_dir) = environment_app();
|
|
let mut body = environment_body("invalid-env", "local");
|
|
body["network"]["mode"] = json!("block");
|
|
|
|
let response = app
|
|
.oneshot(json_request(Method::POST, "/environments", &body))
|
|
.await
|
|
.expect("invalid environment create should respond");
|
|
|
|
response_status(
|
|
response,
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
"POST /api/v1/environments invalid settings",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn relative_environment_cwd_over_rest_returns_unprocessable_entity() {
|
|
let (app, temp_dir, environment_dir) = environment_app();
|
|
let mut body = environment_body("relative-cwd", "local");
|
|
body["cwd"] = json!("relative/workspace");
|
|
|
|
let response = app
|
|
.oneshot(json_request(Method::POST, "/environments", &body))
|
|
.await
|
|
.expect("invalid cwd create should respond");
|
|
let error = response_json(
|
|
response,
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
"POST /api/v1/environments relative cwd",
|
|
)
|
|
.await;
|
|
|
|
assert!(!environment_dir.join("relative-cwd.toml").exists());
|
|
assert!(
|
|
persisted_environment(&temp_dir, "relative-cwd")
|
|
.await
|
|
.is_none()
|
|
);
|
|
let message = serde_json::to_string(&error).expect("error should serialize");
|
|
assert!(
|
|
message.contains("environment.cwd") && message.contains("absolute path"),
|
|
"unexpected error: {message}"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn dockerfile_path_over_rest_is_rejected_without_persisting_or_exposing_contents() {
|
|
let (app, temp_dir, environment_dir) = environment_app();
|
|
tokio::fs::write(
|
|
temp_dir.path().join("Dockerfile"),
|
|
"FROM private.example/secret\n",
|
|
)
|
|
.await
|
|
.expect("secret Dockerfile fixture should be written");
|
|
let mut body = environment_body("path-env", "docker");
|
|
body["image"]["docker"] = Value::Null;
|
|
body["image"]["dockerfile"] = json!({
|
|
"type": "path",
|
|
"path": "Dockerfile"
|
|
});
|
|
|
|
let response = app
|
|
.clone()
|
|
.oneshot(json_request(Method::POST, "/environments", &body))
|
|
.await
|
|
.expect("path Dockerfile create should respond");
|
|
let error = response_json(
|
|
response,
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
"POST /api/v1/environments Dockerfile path",
|
|
)
|
|
.await;
|
|
|
|
assert!(!environment_dir.join("path-env.toml").exists());
|
|
assert!(persisted_environment(&temp_dir, "path-env").await.is_none());
|
|
assert!(
|
|
!serde_json::to_string(&error)
|
|
.expect("error body should serialize")
|
|
.contains("private.example/secret")
|
|
);
|
|
let list = app
|
|
.oneshot(empty_request(Method::GET, "/environments"))
|
|
.await
|
|
.expect("list environments should respond");
|
|
let list = response_json(list, StatusCode::OK, "GET /api/v1/environments").await;
|
|
assert!(
|
|
!list["data"]
|
|
.as_array()
|
|
.expect("environment list data should be an array")
|
|
.iter()
|
|
.any(|environment| environment["id"] == "path-env")
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_environment_removes_non_default_and_default_is_deletable() {
|
|
let (app, temp_dir, environment_dir) = environment_app();
|
|
let created = create_environment(&app, "delete-env", "local").await;
|
|
let revision = revision_from(&created);
|
|
|
|
let response = app
|
|
.clone()
|
|
.oneshot(request_with_if_match(
|
|
Method::DELETE,
|
|
"/environments/delete-env",
|
|
&format!("\"{revision}\""),
|
|
None,
|
|
))
|
|
.await
|
|
.expect("delete environment should respond");
|
|
response_status(
|
|
response,
|
|
StatusCode::NO_CONTENT,
|
|
"DELETE /api/v1/environments/delete-env",
|
|
)
|
|
.await;
|
|
|
|
assert!(!environment_dir.join("delete-env.toml").exists());
|
|
assert!(
|
|
persisted_environment(&temp_dir, "delete-env")
|
|
.await
|
|
.is_none()
|
|
);
|
|
let missing = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/environments/delete-env"))
|
|
.await
|
|
.expect("get deleted environment should respond");
|
|
response_status(
|
|
missing,
|
|
StatusCode::NOT_FOUND,
|
|
"GET /api/v1/environments/delete-env after delete",
|
|
)
|
|
.await;
|
|
|
|
// `default` is an ordinary environment: it can be deleted, which removes the
|
|
// run fallback. The server no longer protects it.
|
|
let default = app
|
|
.clone()
|
|
.oneshot(empty_request(Method::GET, "/environments/default"))
|
|
.await
|
|
.expect("get default environment should respond");
|
|
let default = response_json(default, StatusCode::OK, "GET /api/v1/environments/default").await;
|
|
let deleted = app
|
|
.clone()
|
|
.oneshot(request_with_if_match(
|
|
Method::DELETE,
|
|
"/environments/default",
|
|
revision_from(&default),
|
|
None,
|
|
))
|
|
.await
|
|
.expect("delete default environment should respond");
|
|
response_status(
|
|
deleted,
|
|
StatusCode::NO_CONTENT,
|
|
"DELETE /api/v1/environments/default",
|
|
)
|
|
.await;
|
|
|
|
assert!(!environment_dir.join("default.toml").exists());
|
|
assert!(persisted_environment(&temp_dir, "default").await.is_none());
|
|
let missing_default = app
|
|
.oneshot(empty_request(Method::GET, "/environments/default"))
|
|
.await
|
|
.expect("get deleted default environment should respond");
|
|
response_status(
|
|
missing_default,
|
|
StatusCode::NOT_FOUND,
|
|
"GET /api/v1/environments/default after delete",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn environment_routes_require_authenticated_user() {
|
|
let temp_dir = tempfile::tempdir().expect("environment test tempdir should be created");
|
|
let state = TestAppStateBuilder::new()
|
|
.active_config_path(temp_dir.path().join("settings.toml"))
|
|
.build();
|
|
let app = build_router(state, test_auth_mode());
|
|
|
|
let response = app
|
|
.oneshot(empty_request(Method::GET, "/environments"))
|
|
.await
|
|
.expect("unauthenticated environment list should respond");
|
|
|
|
response_status(
|
|
response,
|
|
StatusCode::UNAUTHORIZED,
|
|
"GET /api/v1/environments without auth",
|
|
)
|
|
.await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn create_environment_refreshes_cached_manifest_run_settings() {
|
|
let (app, _temp_dir) = environment_app_with_default_environment("api-default");
|
|
|
|
let before = system_info(&app).await;
|
|
assert_eq!(before["sandbox_provider"], "local");
|
|
|
|
create_environment(&app, "api-default", "daytona").await;
|
|
|
|
let after = system_info(&app).await;
|
|
assert_eq!(after["sandbox_provider"], "daytona");
|
|
}
|