#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_devcontainer001_missing_image_source() {
let json: Value = serde_json::from_str(r#"{"name": "Invalid"}"#).unwrap();
let result = check_devcontainer001(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER001");
}
#[test]
fn test_devcontainer001_has_image() {
let json: Value = serde_json::from_str(
r#"{"name": "Valid", "image": "mcr.microsoft.com/devcontainers/base:ubuntu"}"#,
)
.unwrap();
let result = check_devcontainer001(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer001_has_build() {
let json: Value =
serde_json::from_str(r#"{"name": "Valid", "build": {"dockerfile": "Dockerfile"}}"#)
.unwrap();
let result = check_devcontainer001(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer001_has_compose() {
let json: Value = serde_json::from_str(
r#"{"name": "Valid", "dockerComposeFile": "docker-compose.yml", "service": "app"}"#,
)
.unwrap();
let result = check_devcontainer001(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer002_latest_tag() {
let json: Value =
serde_json::from_str(r#"{"image": "mcr.microsoft.com/devcontainers/base:latest"}"#)
.unwrap();
let result = check_devcontainer002(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER002");
}
#[test]
fn test_devcontainer002_pinned_version() {
let json: Value = serde_json::from_str(
r#"{"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04"}"#,
)
.unwrap();
let result = check_devcontainer002(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer003_absolute_path() {
let json: Value =
serde_json::from_str(r#"{"build": {"dockerfile": "/absolute/path/Dockerfile"}}"#)
.unwrap();
let result = check_devcontainer003(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER003");
}
#[test]
fn test_devcontainer003_relative_path() {
let json: Value =
serde_json::from_str(r#"{"build": {"dockerfile": "Dockerfile"}}"#).unwrap();
let result = check_devcontainer003(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer004_missing_service() {
let json: Value =
serde_json::from_str(r#"{"dockerComposeFile": "docker-compose.yml"}"#).unwrap();
let result = check_devcontainer004(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER004");
}
#[test]
fn test_devcontainer004_has_service() {
let json: Value = serde_json::from_str(
r#"{"dockerComposeFile": "docker-compose.yml", "service": "app"}"#,
)
.unwrap();
let result = check_devcontainer004(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer005_unknown_option() {
let json: Value = serde_json::from_str(
r#"{"image": "test", "features": {"ghcr.io/test:1": {"unknownOption": "value"}}}"#,
)
.unwrap();
let result = check_devcontainer005(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER005");
}
#[test]
fn test_devcontainer005_valid_options() {
let json: Value = serde_json::from_str(
r#"{"image": "test", "features": {"ghcr.io/test:1": {"version": "18"}}}"#,
)
.unwrap();
let result = check_devcontainer005(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer007_invalid_waitfor() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "waitFor": "invalidStage"}"#).unwrap();
let result = check_devcontainer007(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER007");
}
#[test]
fn test_devcontainer007_valid_waitfor() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "waitFor": "postCreateCommand"}"#).unwrap();
let result = check_devcontainer007(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer008_uid_false() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "updateRemoteUserUID": false}"#).unwrap();
let result = check_devcontainer008(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER008");
}
#[test]
fn test_devcontainer008_uid_true() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "updateRemoteUserUID": true}"#).unwrap();
let result = check_devcontainer008(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer009_relative_workspace() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "workspaceFolder": "relative/path"}"#)
.unwrap();
let result = check_devcontainer009(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER009");
}
#[test]
fn test_devcontainer009_absolute_workspace() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "workspaceFolder": "/workspace"}"#).unwrap();
let result = check_devcontainer009(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer010_non_string_env() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "containerEnv": {"DEBUG": true}}"#).unwrap();
let result = check_devcontainer010(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER010");
}
#[test]
fn test_devcontainer010_string_env() {
let json: Value =
serde_json::from_str(r#"{"image": "test", "containerEnv": {"DEBUG": "true"}}"#)
.unwrap();
let result = check_devcontainer010(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_devcontainer011_invalid_extension() {
let json: Value = serde_json::from_str(
r#"{"image": "test", "customizations": {"vscode": {"extensions": ["invalid-extension-id"]}}}"#,
)
.unwrap();
let result = check_devcontainer011(&json);
assert!(!result.diagnostics.is_empty());
assert_eq!(result.diagnostics[0].code, "DEVCONTAINER011");
}
#[test]
fn test_devcontainer011_valid_extension() {
let json: Value = serde_json::from_str(
r#"{"image": "test", "customizations": {"vscode": {"extensions": ["ms-python.python"]}}}"#,
)
.unwrap();
let result = check_devcontainer011(&json);
assert!(result.diagnostics.is_empty());
}
#[test]
fn test_jsonc_single_line_comment() {
let jsonc = r#"{
// This is a comment
"name": "Test"
}"#;
let result = parse_jsonc(jsonc);
assert!(result.is_ok());
assert_eq!(result.unwrap()["name"], "Test");
}
#[test]
fn test_jsonc_multi_line_comment() {
let jsonc = r#"{
/* Multi-line
comment */
"name": "Test"
}"#;
let result = parse_jsonc(jsonc);
assert!(result.is_ok());
assert_eq!(result.unwrap()["name"], "Test");
}
#[test]
fn test_jsonc_comment_in_string() {
let jsonc = r#"{"name": "// not a comment"}"#;
let result = parse_jsonc(jsonc);
assert!(result.is_ok());
assert_eq!(result.unwrap()["name"], "// not a comment");
}
#[test]
fn test_lint_devcontainer_all_rules() {
let json: Value = serde_json::from_str(r#"{"name": "Invalid"}"#).unwrap();
let result = lint_devcontainer(&json);
assert!(!result.diagnostics.is_empty());
}
#[test]
fn test_lint_devcontainer_valid() {
let json: Value = serde_json::from_str(
r#"{
"name": "Valid Container",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
"forwardPorts": [3000],
"workspaceFolder": "/workspace"
}"#,
)
.unwrap();
let result = lint_devcontainer(&json);
assert!(
result.diagnostics.is_empty(),
"Valid config should have no errors: {:?}",
result.diagnostics
);
}
#[test]
fn test_validate_devcontainer_jsonc() {
let jsonc = r#"{
// Development container
"name": "Dev Container",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04"
}"#;
let result = validate_devcontainer(jsonc);
assert!(result.is_ok());
assert!(result.unwrap().diagnostics.is_empty());
}
#[test]
fn test_list_rules_count() {
let rules = list_devcontainer_rules();
assert_eq!(rules.len(), 11, "Should have 11 DEVCONTAINER rules");
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(proptest::test_runner::Config::with_cases(10))]
#[test]
fn prop_lint_never_panics(json_str in "\\{[^}]*\\}") {
if let Ok(json) = serde_json::from_str::<Value>(&json_str) {
let _ = lint_devcontainer(&json);
}
}
#[test]
fn prop_jsonc_strip_never_panics(content in ".*") {
let _ = strip_json_comments(&content);
}
#[test]
fn prop_valid_image_passes_001(
image in "[a-z]+/[a-z]+:[a-z0-9.-]+"
) {
let json: Value = serde_json::from_str(&format!(
r#"{{"image": "{}"}}"#, image
)).unwrap();
let result = check_devcontainer001(&json);
prop_assert!(result.diagnostics.is_empty());
}
}
}
}