feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
# Provider wire fixtures
|
||||
|
||||
The shared arbiter for the stdin/stdout contract between the desktop
|
||||
(`agents_deploy.rs`) and this provider (spec §Provider Protocol).
|
||||
|
||||
Each `*.request.json` is a request the desktop can emit; each matching
|
||||
`*.response.json` is the exact response this provider produces for it. The
|
||||
provider side is asserted by `tests/wire_fixtures.rs`; the desktop side should
|
||||
assert that its emitted payloads parse as the corresponding request.
|
||||
|
||||
Three rules keep these useful rather than decorative:
|
||||
|
||||
* **Requests are recorded, not invented.** A fixture that no caller emits
|
||||
tests a contract nobody has. "Recorded" means *executed and transcribed* —
|
||||
`deploy-full-launch.request.json` is the output of the desktop's real
|
||||
`build_launch_block` → `deploy_payload_json` path, not a shape derived by
|
||||
reading those functions. Deriving it is how this fixture acquired four
|
||||
impossible values at once: a `respond_to` that was a pubkey where the
|
||||
desktop serializes a kebab-case `RespondTo` enum, allowlist and owner
|
||||
values failing `validate_respond_to_allowlist`'s 64-hex rule
|
||||
(`types.rs:897`), an invented `BUZZ_ACP_PARALLELISM` where the emitter
|
||||
writes `BUZZ_ACP_AGENTS` (`runtime.rs:729`), and a `launch.env` key from
|
||||
no layer of `resolve_effective_harness_descriptor`.
|
||||
* **The provider cannot police this file, so the desktop must.** Every field
|
||||
above is one this provider is deliberately indifferent to — `respond_to` is
|
||||
an opaque `Option<String>`, the allowlist an opaque `Vec<String>`,
|
||||
`policy_env` an arbitrary map — so `the_full_desktop_payload_is_accepted`
|
||||
passes on invented data exactly as happily as on recorded data. The
|
||||
enforcement is the desktop's whole-object equality test, which *builds* the
|
||||
payload and compares it to this file. A completeness guard (the case-list
|
||||
directory scan in `wire_fixtures.rs`) stops a case from going missing; it
|
||||
cannot tell you a case is false.
|
||||
* **Responses are byte-compared after key-sorted re-serialization**, so a
|
||||
field rename or a type change fails here rather than in a desktop that
|
||||
silently reads `undefined`.
|
||||
|
||||
`deploy-*` fixtures cover only responses reachable without a cluster —
|
||||
refusals and malformed input. A successful deploy needs an apiserver and is
|
||||
covered by the conformance suite, not by a static fixture.
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"op": "deploy",
|
||||
"request_id": "req-6",
|
||||
"agent": {
|
||||
"agent_args": [],
|
||||
"agent_command": "goose",
|
||||
"auth_tag": "tag-1",
|
||||
"env_vars": {
|
||||
"USER_KEY": "user-value"
|
||||
},
|
||||
"idle_timeout_seconds": null,
|
||||
"launch": {
|
||||
"args": [
|
||||
"acp"
|
||||
],
|
||||
"command": "goose",
|
||||
"env": {
|
||||
"GOOSE_MODEL": "gpt-5",
|
||||
"GOOSE_PROVIDER": "openai",
|
||||
"USER_KEY": "user-value"
|
||||
},
|
||||
"owner_pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"policy_env": {
|
||||
"BUZZ_ACP_AGENTS": "10",
|
||||
"BUZZ_ACP_DISPLAY_NAME": "worker",
|
||||
"BUZZ_ACP_LAZY_POOL": "true",
|
||||
"BUZZ_ACP_MODEL": "gpt-5",
|
||||
"BUZZ_ACP_RELAY_OBSERVER": "true",
|
||||
"BUZZ_ACP_SESSION_TITLE": "worker",
|
||||
"GOOSE_MODE": "auto"
|
||||
}
|
||||
},
|
||||
"max_turn_duration_seconds": null,
|
||||
"model": "gpt-5",
|
||||
"name": "worker",
|
||||
"parallelism": 10,
|
||||
"private_key_nsec": "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
"provider": "openai",
|
||||
"relay_url": "wss://relay.example",
|
||||
"respond_to": "allowlist",
|
||||
"respond_to_allowlist": [
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||
],
|
||||
"system_prompt": null,
|
||||
"turn_timeout_seconds": 300
|
||||
},
|
||||
"provider_config": {
|
||||
"namespace": "buzz-agents-test",
|
||||
"image": "ghcr.io/block/buzz-sprig@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"inactivity_seconds": 3600
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"op": "deploy",
|
||||
"request_id": "req-5",
|
||||
"agent": {
|
||||
"relay_url": "wss://relay.example",
|
||||
"private_key_nsec": "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5"
|
||||
},
|
||||
"provider_config": {"namespace": "buzz-agents-test", "image": "ghcr.io/block/buzz-sprig@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"deploy refused: neither auth_tag nor launch.owner_pubkey resolved — without an owner the agent cannot honor !shutdown"}
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"op": "deploy",
|
||||
"request_id": "req-3",
|
||||
"agent": {
|
||||
"relay_url": "wss://relay.example",
|
||||
"private_key_nsec": "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
"auth_tag": "tag-1",
|
||||
"provider": " relay-mesh "
|
||||
},
|
||||
"provider_config": {"namespace": "buzz-agents-test", "image": "ghcr.io/block/buzz-sprig@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"deploy refused: this agent is configured for shared compute (relay-mesh), which runs on the relay rather than in a pod. Switch the agent to a local runtime before deploying it to Kubernetes."}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"op": "deploy",
|
||||
"request_id": "req-2",
|
||||
"agent": {
|
||||
"name": "mesh-agent",
|
||||
"relay_url": "wss://relay.example",
|
||||
"private_key_nsec": "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
"auth_tag": "tag-1",
|
||||
"provider": "relay-mesh"
|
||||
},
|
||||
"provider_config": {"namespace": "buzz-agents-test", "image": "ghcr.io/block/buzz-sprig@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"deploy refused: this agent is configured for shared compute (relay-mesh), which runs on the relay rather than in a pod. Switch the agent to a local runtime before deploying it to Kubernetes."}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"op": "deploy",
|
||||
"request_id": "req-4",
|
||||
"agent": {
|
||||
"relay_url": "wss://relay.example",
|
||||
"private_key_nsec": "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
|
||||
"auth_tag": "tag-1"
|
||||
},
|
||||
"provider_config": {"namespace": "buzz-agents-test", "image": "ghcr.io/block/buzz-sprig:latest"}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"provider_config.image \"ghcr.io/block/buzz-sprig:latest\" is not digest-pinned: a tag is a mutable pointer, and this object runs with the agent's private key. Use name@sha256:<64 hex chars>"}
|
||||
@@ -0,0 +1 @@
|
||||
{"op":"info","request_id":"req-1"}
|
||||
@@ -0,0 +1,222 @@
|
||||
//! Golden wire fixtures (spec §Provider Protocol).
|
||||
//!
|
||||
//! These drive the **built binary** over a real pipe rather than calling an
|
||||
//! in-process function: the contract the desktop depends on is
|
||||
//! `stdin → one JSON object on stdout → exit code`, and an in-process test
|
||||
//! would assert the shape of a value while skipping the three things that
|
||||
//! actually break — the process writing nothing, writing two objects, or
|
||||
//! signalling the outcome through the exit code.
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
fn fixtures() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/provider-wire")
|
||||
}
|
||||
|
||||
/// Feed one request to the binary; return `(stdout, exit code)`.
|
||||
fn run(request: &str) -> (String, i32) {
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_buzz-backend-kubernetes"))
|
||||
// A kubeconfig that does not exist, so a fixture that accidentally
|
||||
// reaches the cluster fails loudly here instead of depending on
|
||||
// whatever cluster the developer is pointed at.
|
||||
.env("KUBECONFIG", "/nonexistent/kubeconfig-for-fixture-tests")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("could not run the provider binary");
|
||||
child
|
||||
.stdin
|
||||
.take()
|
||||
.expect("no stdin")
|
||||
.write_all(request.as_bytes())
|
||||
.expect("could not write the request");
|
||||
let out = child.wait_with_output().expect("provider did not exit");
|
||||
(
|
||||
String::from_utf8(out.stdout).expect("stdout was not UTF-8"),
|
||||
out.status.code().unwrap_or(-1),
|
||||
)
|
||||
}
|
||||
|
||||
fn read(name: &str) -> String {
|
||||
std::fs::read_to_string(fixtures().join(name))
|
||||
.unwrap_or_else(|e| panic!("could not read fixture {name}: {e}"))
|
||||
}
|
||||
|
||||
/// Every response fixture, byte-compared after key-sorted re-serialization so
|
||||
/// a field rename fails here rather than in a desktop reading `undefined`.
|
||||
#[test]
|
||||
fn responses_match_their_fixtures() {
|
||||
let cases = [
|
||||
"deploy-relay-mesh",
|
||||
"deploy-relay-mesh-padded",
|
||||
"deploy-tag-image",
|
||||
"deploy-no-owner",
|
||||
];
|
||||
// The list must cover every response fixture on disk. A literal array is
|
||||
// never empty, so `!is_empty()` would assert nothing; what can actually go
|
||||
// wrong is a fixture added to the directory and never added here, which
|
||||
// reads as a passing suite that exercises one case fewer than it appears to.
|
||||
let mut on_disk: Vec<String> = std::fs::read_dir(fixtures())
|
||||
.expect("could not read the fixture directory")
|
||||
.filter_map(|entry| entry.ok()?.file_name().into_string().ok())
|
||||
.filter_map(|name| Some(name.strip_suffix(".response.json")?.to_string()))
|
||||
.collect();
|
||||
on_disk.sort();
|
||||
let mut listed: Vec<String> = cases.iter().map(|c| c.to_string()).collect();
|
||||
listed.sort();
|
||||
assert_eq!(on_disk, listed, "response fixtures and cases disagree");
|
||||
|
||||
for case in cases {
|
||||
let (stdout, code) = run(&read(&format!("{case}.request.json")));
|
||||
assert_eq!(code, 0, "{case}: a produced response must exit 0");
|
||||
|
||||
// Exactly one object, terminated by exactly one newline. Two responses
|
||||
// would leave the desktop's reader holding a second one forever.
|
||||
assert_eq!(
|
||||
stdout.matches('\n').count(),
|
||||
1,
|
||||
"{case}: expected exactly one line, got {stdout:?}"
|
||||
);
|
||||
|
||||
let actual: serde_json::Value =
|
||||
serde_json::from_str(&stdout).unwrap_or_else(|e| panic!("{case}: {e}: {stdout:?}"));
|
||||
let expected: serde_json::Value =
|
||||
serde_json::from_str(&read(&format!("{case}.response.json"))).unwrap();
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
"{case}: response drifted from its fixture"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `info` is checked on the fields the desktop reads rather than byte-for-byte:
|
||||
/// the namespace default is randomly generated per call (§K8s Namespace), so a
|
||||
/// golden copy of it would be a test that fails every run.
|
||||
#[test]
|
||||
fn info_response_carries_the_contract_fields() {
|
||||
let (stdout, code) = run(&read("info.request.json"));
|
||||
assert_eq!(code, 0);
|
||||
let info: serde_json::Value = serde_json::from_str(&stdout).unwrap();
|
||||
assert_eq!(info["ok"], true);
|
||||
assert_eq!(info["protocol_version"], 1);
|
||||
assert_eq!(info["name"], "kubernetes");
|
||||
let schema = &info["config_schema"];
|
||||
assert_eq!(
|
||||
schema["required"],
|
||||
serde_json::json!(["namespace", "image"])
|
||||
);
|
||||
let default = schema["properties"]["namespace"]["default"]
|
||||
.as_str()
|
||||
.expect("no generated namespace default");
|
||||
assert!(
|
||||
default.starts_with("buzz-agents-"),
|
||||
"unexpected namespace default: {default}"
|
||||
);
|
||||
let image_default = schema["properties"]["image"]["default"]
|
||||
.as_str()
|
||||
.expect("no image default");
|
||||
assert!(
|
||||
image_default.starts_with("ghcr.io/block/buzz-sprig:")
|
||||
&& image_default.contains("@sha256:"),
|
||||
"unexpected image default: {image_default}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The desktop's richest payload must parse. No response fixture: this one
|
||||
/// reaches the cluster, so its outcome depends on a kubeconfig. What it
|
||||
/// guards is that every field the desktop sends is *accepted* — a payload the
|
||||
/// provider rejects at parse time is a deploy that never starts.
|
||||
#[test]
|
||||
fn the_full_desktop_payload_is_accepted() {
|
||||
let (stdout, code) = run(&read("deploy-full-launch.request.json"));
|
||||
assert_eq!(code, 0);
|
||||
let response: serde_json::Value = serde_json::from_str(&stdout).unwrap();
|
||||
let error = response["error"].as_str().unwrap_or_default();
|
||||
// It fails — there is no cluster — but it must fail at the *connection*,
|
||||
// having accepted every field above it.
|
||||
assert!(
|
||||
error.contains("kubeconfig"),
|
||||
"the full payload was rejected before reaching the cluster: {error}"
|
||||
);
|
||||
}
|
||||
|
||||
/// Sami's pre-registered respond-to matrix, driven through the built binary.
|
||||
///
|
||||
/// `build_env` runs at `main.rs:124`, `client::connect` at `:132`, so under a
|
||||
/// kubeconfig that cannot exist the error string *is* the ordering assertion:
|
||||
/// "kubeconfig" means the gate passed and we reached the cluster, anything
|
||||
/// else means we refused before writing a Secret. A test asserting only
|
||||
/// `ok: false` would pass on the connection error and prove nothing.
|
||||
///
|
||||
/// The cases are applied to the real full-launch request so each one differs
|
||||
/// from a known-good deploy in exactly the field under test.
|
||||
#[test]
|
||||
fn the_respond_to_gate_matches_the_harness_acceptance_surface() {
|
||||
let key_a = "a".repeat(64);
|
||||
let padded_upper = format!(" {} ", "A".repeat(64));
|
||||
// (name, respond_to, allowlist, must reach the cluster)
|
||||
let cases: Vec<(&str, &str, Option<Vec<String>>, bool)> = vec![
|
||||
("allowlist + []", "allowlist", Some(vec![]), false),
|
||||
("allowlist + absent", "allowlist", None, false),
|
||||
(
|
||||
"allowlist + junk",
|
||||
"allowlist",
|
||||
Some(vec!["beefcafe".into()]),
|
||||
false,
|
||||
),
|
||||
("unparseable mode", "npub1abc", None, false),
|
||||
("padded mode", " allowlist ", None, false),
|
||||
(
|
||||
"allowlist + two valid",
|
||||
"allowlist",
|
||||
Some(vec![key_a.clone(), "b".repeat(64)]),
|
||||
true,
|
||||
),
|
||||
(
|
||||
"owner-only + junk list",
|
||||
"owner-only",
|
||||
Some(vec!["beefcafe".into()]),
|
||||
true,
|
||||
),
|
||||
(
|
||||
"allowlist + padded upper",
|
||||
"allowlist",
|
||||
Some(vec![padded_upper]),
|
||||
true,
|
||||
),
|
||||
("nobody", "nobody", None, true),
|
||||
("anyone", "anyone", None, true),
|
||||
];
|
||||
|
||||
let base: serde_json::Value =
|
||||
serde_json::from_str(&read("deploy-full-launch.request.json")).unwrap();
|
||||
|
||||
for (name, mode, allowlist, reaches_cluster) in cases {
|
||||
let mut request = base.clone();
|
||||
let agent = &mut request["agent"];
|
||||
agent["respond_to"] = serde_json::json!(mode);
|
||||
agent["respond_to_allowlist"] = match &allowlist {
|
||||
Some(list) => serde_json::json!(list),
|
||||
None => serde_json::Value::Null,
|
||||
};
|
||||
|
||||
let (stdout, code) = run(&request.to_string());
|
||||
assert_eq!(code, 0, "{name}: provider did not exit cleanly");
|
||||
let response: serde_json::Value = serde_json::from_str(&stdout).unwrap();
|
||||
let error = response["error"].as_str().unwrap_or_default();
|
||||
let reached = error.contains("kubeconfig");
|
||||
|
||||
assert_eq!(
|
||||
reached, reaches_cluster,
|
||||
"{name}: expected reaches_cluster={reaches_cluster}, got error: {error}"
|
||||
);
|
||||
if !reaches_cluster {
|
||||
assert!(
|
||||
error.contains("deploy refused"),
|
||||
"{name}: refused, but not by the gate: {error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user