use std::process::Command;
use greentic_extension_sdk_contract::ExtensionKind;
use greentic_extension_sdk_testing::ExtensionFixtureBuilder;
use tempfile::TempDir;
fn gtdx_bin() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_BIN_EXE_gtdx"))
}
fn well_formed_addon() -> serde_json::Value {
serde_json::json!({
"id": "qdrant",
"family": "vector-db",
"display_name": "Qdrant",
"description": "Vector database.",
"config_schema": "{\"type\":\"object\"}",
"desired_state_schema": "{\"type\":\"object\",\"properties\":{\"collections\":{\"type\":\"array\"}}}",
"outputs": [
{ "name": "QDRANT_URL", "type": "text" },
{ "name": "QDRANT_API_KEY", "type": "text", "sensitive": true }
]
})
}
fn fixture_with_addons(
addons: serde_json::Value,
) -> greentic_extension_sdk_testing::ExtensionFixture {
let fixture =
ExtensionFixtureBuilder::new(ExtensionKind::Design, "greentic.addon-test", "0.1.0")
.offer("greentic:addon-test/y", "1.0.0")
.with_wasm(b"wasm".to_vec())
.build()
.expect("fixture must build");
let bytes = std::fs::read(&fixture.describe_path).expect("describe.json must be readable");
let mut describe: serde_json::Value =
serde_json::from_slice(&bytes).expect("describe.json must be valid JSON");
describe["contributions"]["addons"] = addons;
describe["$schema"] =
serde_json::json!("https://store.greentic.cloud/schemas/describe-v2.json");
if let Some(obj) = describe.as_object_mut() {
obj.remove("engine");
}
std::fs::write(
&fixture.describe_path,
serde_json::to_vec_pretty(&describe).expect("describe.json must re-serialize"),
)
.expect("describe.json must be writable");
fixture
}
fn empty_home() -> TempDir {
TempDir::new().expect("tmp home must create")
}
#[test]
fn a_well_formed_addon_passes_validate() {
let fixture = fixture_with_addons(serde_json::json!([well_formed_addon()]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("validate")
.arg(fixture.root())
.output()
.expect("gtdx validate must run");
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn a_well_formed_addon_passes_lint_with_no_addon_violations() {
let fixture = fixture_with_addons(serde_json::json!([well_formed_addon()]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("lint")
.arg("--dir")
.arg(fixture.root())
.output()
.expect("gtdx lint must run");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success(), "stderr: {stderr}");
for code in [
"E_ADDON_ID_PATTERN",
"E_ADDON_OUTPUT_NAME",
"E_ADDON_SECRET_IN_DESIRED_STATE",
"W_ADDON_FAMILY_UNKNOWN",
] {
assert!(
!stderr.contains(code),
"a well-formed addon must not trip {code}: {stderr}"
);
}
}
#[test]
fn a_top_level_secret_in_desired_state_fails_lint() {
let mut addon = well_formed_addon();
addon["desired_state_schema"] = serde_json::json!(
"{\"type\":\"object\",\"properties\":{\"collections\":{\"type\":\"array\"},\"admin_password\":{\"type\":\"string\"}}}"
);
let fixture = fixture_with_addons(serde_json::json!([addon]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("lint")
.arg("--dir")
.arg(fixture.root())
.output()
.expect("gtdx lint must run");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"a top-level secret in desired_state_schema must fail lint"
);
assert!(
stderr.contains("E_ADDON_SECRET_IN_DESIRED_STATE"),
"stderr must name the rule: {stderr}"
);
}
#[test]
fn a_secret_nested_inside_items_fails_lint() {
let mut addon = well_formed_addon();
addon["desired_state_schema"] = serde_json::json!(
"{\"type\":\"object\",\"properties\":{\"acl_users\":{\"type\":\"array\",\"items\":{\"type\":\"object\",\"properties\":{\"password\":{}}}}}}"
);
let fixture = fixture_with_addons(serde_json::json!([addon]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("lint")
.arg("--dir")
.arg(fixture.root())
.output()
.expect("gtdx lint must run");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"a secret nested inside items must fail lint"
);
assert!(
stderr.contains("E_ADDON_SECRET_IN_DESIRED_STATE"),
"stderr must name the rule: {stderr}"
);
}
#[test]
fn an_id_the_schema_rejects_fails_validate() {
let mut addon = well_formed_addon();
addon["id"] = serde_json::json!("Qdrant/Primary");
let fixture = fixture_with_addons(serde_json::json!([addon]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("validate")
.arg(fixture.root())
.output()
.expect("gtdx validate must run");
assert!(
!output.status.success(),
"an id containing '/' and uppercase must fail validate"
);
}
#[test]
fn a_duplicate_addon_id_fails_validate() {
let mut second = well_formed_addon();
second["display_name"] = serde_json::json!("Qdrant (again)");
let fixture = fixture_with_addons(serde_json::json!([well_formed_addon(), second]));
let home = empty_home();
let output = Command::new(gtdx_bin())
.arg("--home")
.arg(home.path())
.arg("validate")
.arg(fixture.root())
.output()
.expect("gtdx validate must run");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"a duplicate addon id must fail validate"
);
assert!(
stderr.contains("qdrant"),
"error should name the offending id: {stderr}"
);
}