mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-05 08:10:39 +00:00
209 lines
6.3 KiB
Rust
209 lines
6.3 KiB
Rust
use fabro_devcontainer::{Command, DevcontainerResolver};
|
|
|
|
use super::helpers::fixture_path;
|
|
|
|
#[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, 80, 9090]);
|
|
assert_eq!(
|
|
config.environment.get("EDITOR").map(String::as_str),
|
|
Some("code")
|
|
);
|
|
assert_eq!(config.workspace_folder, "/workspaces/image-only");
|
|
assert!(config.compose_files.is_empty());
|
|
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"));
|
|
|
|
// onCreateCommand
|
|
assert_eq!(config.on_create_commands.len(), 1);
|
|
assert!(matches!(&config.on_create_commands[0], Command::Shell(s) if s == "setup.sh"));
|
|
|
|
// containerEnv baked into Dockerfile
|
|
assert!(
|
|
config
|
|
.dockerfile
|
|
.contains("ENV DEBIAN_FRONTEND=noninteractive")
|
|
);
|
|
assert_eq!(
|
|
config
|
|
.container_env
|
|
.get("DEBIAN_FRONTEND")
|
|
.map(String::as_str),
|
|
Some("noninteractive")
|
|
);
|
|
}
|
|
|
|
#[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"));
|
|
|
|
// build.args
|
|
assert_eq!(
|
|
config.build_args.get("NODE_VERSION").map(String::as_str),
|
|
Some("20")
|
|
);
|
|
|
|
// build.target
|
|
assert_eq!(config.build_target.as_deref(), Some("dev"));
|
|
}
|
|
|
|
#[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_eq!(config.compose_files.len(), 1);
|
|
assert_eq!(config.compose_service.as_deref(), Some("app"));
|
|
|
|
// Ports come from compose + forwardPorts merged
|
|
assert_eq!(config.forwarded_ports, vec![3000, 9229, 5173]);
|
|
|
|
// 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_compose_multi() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("compose-multi"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Override file wins for image
|
|
assert!(config.dockerfile.contains("FROM node:22"));
|
|
assert_eq!(config.workspace_folder, "/workspace");
|
|
assert_eq!(config.compose_files.len(), 2);
|
|
assert_eq!(config.compose_service.as_deref(), Some("app"));
|
|
|
|
// Port from base.yml
|
|
assert_eq!(config.forwarded_ports, vec![3000]);
|
|
|
|
// Environment from override.yml
|
|
assert_eq!(
|
|
config.environment.get("OVERRIDE_VAR").map(String::as_str),
|
|
Some("true")
|
|
);
|
|
}
|
|
|
|
#[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 resolve_subdirectory_mode() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-mode"))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(
|
|
config
|
|
.dockerfile
|
|
.contains("FROM mcr.microsoft.com/devcontainers/python:3.12")
|
|
);
|
|
assert_eq!(config.remote_user.as_deref(), Some("vscode"));
|
|
assert_eq!(config.workspace_folder, "/workspaces/subdirectory-mode");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_subdirectory_multiple_picks_alphabetical_first() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-multiple"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// "alpha" sorts before "beta", so alpha's config is used
|
|
assert!(
|
|
config
|
|
.dockerfile
|
|
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
|
|
);
|
|
assert_eq!(config.remote_user.as_deref(), Some("alpha-user"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn resolve_subdirectory_standard_wins_over_subdirs() {
|
|
let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-with-standard"))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Standard .devcontainer/devcontainer.json takes priority over subdirectory
|
|
// format
|
|
assert!(
|
|
config
|
|
.dockerfile
|
|
.contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
|
|
);
|
|
assert_eq!(config.remote_user.as_deref(), Some("standard-user"));
|
|
}
|
|
|
|
#[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 fabro-devcontainer")
|
|
);
|
|
// Should have the base image
|
|
assert!(config.dockerfile.contains("FROM"));
|
|
// Should end with a newline
|
|
assert!(config.dockerfile.ends_with('\n'));
|
|
}
|