mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
Standalone crate that reads devcontainer.json (with JSONC support), fetches OCI Features via oras, and produces a resolved config containing a generated Dockerfile, lifecycle hooks, environment variables, and forwarded ports. Supports image, Dockerfile, and Docker Compose modes with devcontainer variable substitution. No coupling to arc-workflows or DaytonaSandbox. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
102 lines
3.6 KiB
Rust
102 lines
3.6 KiB
Rust
use arc_devcontainer::{Command, DevcontainerResolver};
|
|
use std::path::PathBuf;
|
|
|
|
fn fixture_path(name: &str) -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures")
|
|
.join(name)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_image_only() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("image-only"))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(config.dockerfile.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu"));
|
|
assert_eq!(config.remote_user.as_deref(), Some("vscode"));
|
|
assert_eq!(config.forwarded_ports, vec![3000, 8080]);
|
|
assert_eq!(config.environment.get("EDITOR").map(String::as_str), Some("code"));
|
|
assert_eq!(config.workspace_folder, "/workspaces/image-only");
|
|
assert!(config.compose_file.is_none());
|
|
assert!(config.compose_service.is_none());
|
|
|
|
assert_eq!(config.post_create_commands.len(), 1);
|
|
assert!(matches!(&config.post_create_commands[0], Command::Shell(s) if s == "echo hello"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_dockerfile_mode() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("dockerfile-mode"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Should read the actual Dockerfile content
|
|
assert!(config.dockerfile.contains("FROM node:20"));
|
|
assert!(config.dockerfile.contains("apt-get update"));
|
|
assert_eq!(config.remote_user.as_deref(), Some("developer"));
|
|
assert_eq!(config.forwarded_ports, vec![4000]);
|
|
|
|
assert_eq!(config.post_create_commands.len(), 1);
|
|
assert!(matches!(&config.post_create_commands[0], Command::Shell(s) if s == "npm install"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_compose_mode() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("compose-mode"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// In compose mode, the dockerfile is derived from the compose service's image
|
|
assert!(config.dockerfile.contains("FROM node:20"));
|
|
assert_eq!(config.workspace_folder, "/workspace");
|
|
assert_eq!(config.remote_user.as_deref(), Some("node"));
|
|
assert!(config.compose_file.is_some());
|
|
assert_eq!(config.compose_service.as_deref(), Some("app"));
|
|
|
|
// Ports come from compose + remoteEnv merged
|
|
assert_eq!(config.forwarded_ports, vec![3000, 9229]);
|
|
|
|
// Environment merged from compose + remoteEnv
|
|
assert_eq!(config.environment.get("NODE_ENV").map(String::as_str), Some("development"));
|
|
assert_eq!(config.environment.get("DEBUG").map(String::as_str), Some("true"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_variables() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("variables"))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(config.workspace_folder, "/workspaces/variables");
|
|
assert_eq!(
|
|
config.environment.get("PROJECT_ROOT").map(String::as_str),
|
|
Some("/workspaces/variables")
|
|
);
|
|
assert_eq!(
|
|
config.environment.get("PROJECT_NAME").map(String::as_str),
|
|
Some("variables")
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_not_found() {
|
|
let result = DevcontainerResolver::resolve(&fixture_path("nonexistent")).await;
|
|
assert!(result.is_err());
|
|
let err = result.unwrap_err();
|
|
assert!(err.to_string().contains("no devcontainer.json found"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn generated_dockerfile_is_well_formed() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("image-only"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Should start with the generated header
|
|
assert!(config.dockerfile.contains("# Generated by arc-devcontainer"));
|
|
// Should have the base image
|
|
assert!(config.dockerfile.contains("FROM"));
|
|
// Should end with a newline
|
|
assert!(config.dockerfile.ends_with('\n'));
|
|
}
|