#![allow(dead_code)]
use std::sync::{Mutex, MutexGuard, OnceLock};
fn env_mutex() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
pub struct EnvGuard {
key: String,
prev: Option<String>,
_lock: MutexGuard<'static, ()>,
}
impl EnvGuard {
pub fn set(key: &str, value: &str) -> Self {
let lock = env_mutex()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let prev = std::env::var(key).ok();
#[allow(unsafe_code)]
unsafe {
std::env::set_var(key, value);
}
EnvGuard {
key: key.to_string(),
prev,
_lock: lock,
}
}
pub fn remove(key: &str) -> Self {
let lock = env_mutex()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let prev = std::env::var(key).ok();
#[allow(unsafe_code)]
unsafe {
std::env::remove_var(key);
}
EnvGuard {
key: key.to_string(),
prev,
_lock: lock,
}
}
}
impl Drop for EnvGuard {
fn drop(&mut self) {
#[allow(unsafe_code)]
unsafe {
match &self.prev {
Some(v) => std::env::set_var(&self.key, v),
None => std::env::remove_var(&self.key),
}
}
}
}
pub fn populate_gtpack_for_local_load(
describe: &mut greentic_extension_sdk_contract::DescribeJson,
) {
use greentic_extension_sdk_contract::describe::provider::RuntimeGtpack;
for component in describe.runtime.components.values_mut() {
if component.gtpack.is_none() {
component.gtpack = Some(RuntimeGtpack {
file: "extension.wasm".to_string(),
sha256: "0".repeat(64),
pack_id: describe.metadata.id.clone(),
component_version: describe.metadata.version.clone(),
});
}
}
}
fn walk_files(dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut out = Vec::new();
for ent in std::fs::read_dir(dir).unwrap().flatten() {
let path = ent.path();
if path.is_dir() {
out.extend(walk_files(&path));
} else {
out.push(path);
}
}
out
}
pub fn build_dir_manifest_bytes(dir: &std::path::Path) -> Vec<u8> {
let mut entries: Vec<(String, Vec<u8>)> = Vec::new();
for path in walk_files(dir) {
let rel = path
.strip_prefix(dir)
.unwrap()
.to_string_lossy()
.replace('\\', "/");
if rel == greentic_extension_sdk_contract::MANIFEST_ENTRY_NAME || rel.is_empty() {
continue;
}
entries.push((rel, std::fs::read(&path).unwrap()));
}
let manifest = greentic_extension_sdk_contract::build_manifest(
entries.iter().map(|(p, b)| (p.as_str(), b.as_slice())),
);
serde_jcs::to_vec(&manifest).unwrap()
}
pub fn finalize_signed_with_manifest(
dir: &std::path::Path,
describe: &mut greentic_extension_sdk_contract::DescribeJson,
sk: &ed25519_dalek::SigningKey,
) {
let manifest_bytes = build_dir_manifest_bytes(dir);
std::fs::write(
dir.join(greentic_extension_sdk_contract::MANIFEST_ENTRY_NAME),
&manifest_bytes,
)
.unwrap();
greentic_extension_sdk_contract::bind_manifest(describe, &manifest_bytes);
greentic_extension_sdk_contract::sign_describe(describe, sk).expect("sign");
std::fs::write(
dir.join("describe.json"),
serde_json::to_string_pretty(describe).unwrap(),
)
.unwrap();
}
pub fn signed_fixture(
kind: greentic_extension_sdk_contract::ExtensionKind,
id: &str,
version: &str,
) -> (
greentic_extension_sdk_testing::ExtensionFixture,
ed25519_dalek::SigningKey,
) {
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
let minimal_wasm = wat::parse_str(r"(component)").expect("wat component must compile");
let fixture = greentic_extension_sdk_testing::ExtensionFixtureBuilder::new(kind, id, version)
.offer("greentic:test/ping", "1.0.0")
.with_wasm(minimal_wasm)
.build()
.expect("fixture build");
let describe_path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&describe_path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
populate_gtpack_for_local_load(&mut describe);
let sk = SigningKey::generate(&mut OsRng);
finalize_signed_with_manifest(fixture.root(), &mut describe, &sk);
(fixture, sk)
}
pub fn signed_provider_fixture_with_placeholder_gtpack(
id: &str,
version: &str,
) -> (
greentic_extension_sdk_testing::ExtensionFixture,
ed25519_dalek::SigningKey,
) {
use ed25519_dalek::SigningKey;
use greentic_extension_sdk_contract::describe::provider::RuntimeGtpack;
use rand::rngs::OsRng;
let minimal_wasm = wat::parse_str(r"(component)").expect("wat component must compile");
let fixture = greentic_extension_sdk_testing::ExtensionFixtureBuilder::new(
greentic_extension_sdk_contract::ExtensionKind::Provider,
id,
version,
)
.offer("greentic:test/ping", "1.0.0")
.with_wasm(minimal_wasm)
.build()
.expect("fixture build");
let runtime_dir = fixture.root().join("runtime");
std::fs::create_dir_all(&runtime_dir).unwrap();
std::fs::write(
runtime_dir.join("provider.gtpack"),
b"placeholder - not valid WASM; runner-host fetches the real pack",
)
.unwrap();
let describe_path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&describe_path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
for component in describe.runtime.components.values_mut() {
component.gtpack = Some(RuntimeGtpack {
file: "runtime/provider.gtpack".to_string(),
sha256: "0".repeat(64),
pack_id: describe.metadata.id.clone(),
component_version: describe.metadata.version.clone(),
});
}
let sk = SigningKey::generate(&mut OsRng);
finalize_signed_with_manifest(fixture.root(), &mut describe, &sk);
(fixture, sk)
}
pub fn signed_fixture_without_root_wasm(
kind: greentic_extension_sdk_contract::ExtensionKind,
id: &str,
version: &str,
) -> (
greentic_extension_sdk_testing::ExtensionFixture,
ed25519_dalek::SigningKey,
) {
use ed25519_dalek::SigningKey;
use greentic_extension_sdk_contract::describe::provider::RuntimeGtpack;
use rand::rngs::OsRng;
let minimal_wasm = wat::parse_str(r"(component)").expect("wat component must compile");
let fixture = greentic_extension_sdk_testing::ExtensionFixtureBuilder::new(kind, id, version)
.offer("greentic:test/ping", "1.0.0")
.with_wasm(minimal_wasm)
.build()
.expect("fixture build");
let root_wasm = fixture.root().join("extension.wasm");
let inner_wasm = fixture.root().join("inner.wasm");
std::fs::rename(&root_wasm, &inner_wasm).expect("rename extension.wasm → inner.wasm");
let describe_path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&describe_path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
for component in describe.runtime.components.values_mut() {
component.gtpack = Some(RuntimeGtpack {
file: "inner.wasm".to_string(),
sha256: "0".repeat(64),
pack_id: describe.metadata.id.clone(),
component_version: describe.metadata.version.clone(),
});
}
let sk = SigningKey::generate(&mut OsRng);
finalize_signed_with_manifest(fixture.root(), &mut describe, &sk);
(fixture, sk)
}
pub fn signed_fixture_with_placeholder_gtpack(
kind: greentic_extension_sdk_contract::ExtensionKind,
id: &str,
version: &str,
) -> (
greentic_extension_sdk_testing::ExtensionFixture,
ed25519_dalek::SigningKey,
) {
use ed25519_dalek::SigningKey;
use greentic_extension_sdk_contract::describe::provider::RuntimeGtpack;
use rand::rngs::OsRng;
let minimal_wasm = wat::parse_str(r"(component)").expect("wat component must compile");
let fixture = greentic_extension_sdk_testing::ExtensionFixtureBuilder::new(kind, id, version)
.offer("greentic:test/ping", "1.0.0")
.with_wasm(minimal_wasm)
.build()
.expect("fixture build");
let runtime_dir = fixture.root().join("runtime");
std::fs::create_dir_all(&runtime_dir).unwrap();
std::fs::write(
runtime_dir.join("component.gtpack"),
b"placeholder - not valid WASM; runner-host fetches the real pack",
)
.unwrap();
let describe_path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&describe_path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
for component in describe.runtime.components.values_mut() {
component.gtpack = Some(RuntimeGtpack {
file: "runtime/component.gtpack".to_string(),
sha256: "0".repeat(64),
pack_id: describe.metadata.id.clone(),
component_version: describe.metadata.version.clone(),
});
}
let sk = SigningKey::generate(&mut OsRng);
finalize_signed_with_manifest(fixture.root(), &mut describe, &sk);
(fixture, sk)
}
pub fn tamper_fixture(fixture: &greentic_extension_sdk_testing::ExtensionFixture) {
let path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
describe.metadata.version = "99.99.99".into();
std::fs::write(&path, serde_json::to_string_pretty(&describe).unwrap()).unwrap();
}
pub fn unsigned_fixture(
kind: greentic_extension_sdk_contract::ExtensionKind,
id: &str,
version: &str,
) -> greentic_extension_sdk_testing::ExtensionFixture {
let minimal_wasm = wat::parse_str(r"(component)").expect("wat component must compile");
let fixture = greentic_extension_sdk_testing::ExtensionFixtureBuilder::new(kind, id, version)
.offer("greentic:test/ping", "1.0.0")
.with_wasm(minimal_wasm)
.build()
.expect("fixture build");
let describe_path = fixture.root().join("describe.json");
let raw = std::fs::read_to_string(&describe_path).unwrap();
let mut describe: greentic_extension_sdk_contract::DescribeJson =
serde_json::from_str(&raw).unwrap();
populate_gtpack_for_local_load(&mut describe);
std::fs::write(
&describe_path,
serde_json::to_string_pretty(&describe).unwrap(),
)
.unwrap();
fixture
}