Synced from monorepo
Changes: - Persist submitter identity for /feedback - Let custom models use rotating tokens from named auth providers - Template stale tool/param name literals in server-native descriptions - Minimal mode commits thinking in full, lookups as one-liners - Tighten durable append internals - Nudge model to end turn on no-op bash commands - Per-fetch signing nonce in the managed-config envelope, with a server-side replay probe - Include working tree in startup status
This commit is contained in:
parent
ba76b0a683
commit
a881e6703f
140 changed files with 6746 additions and 2377 deletions
316
crates/codegen/xai-grok-shell/tests/test_auth_provider_e2e.rs
Normal file
316
crates/codegen/xai-grok-shell/tests/test_auth_provider_e2e.rs
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
//! End-to-end test for `[auth_provider.<name>]` per-model credential helpers.
|
||||
//!
|
||||
//! Runs the built grok binary headless against the mock inference server with
|
||||
//! a config-defined BYOK model whose bearer comes from a mock auth binary (a
|
||||
//! script the test writes to disk). The harness's `XAI_API_KEY` stands in for
|
||||
//! the session-tier credential.
|
||||
//!
|
||||
//! `#[ignore]` (needs a built binary). The CI lifecycle lanes run it against
|
||||
//! the release artifact via `GROK_BINARY`; run locally (auto-builds the pager):
|
||||
//! ```bash
|
||||
//! cargo test -p xai-grok-shell --test test_auth_provider_e2e -- --ignored
|
||||
//! ```
|
||||
//!
|
||||
//! Unix-only: the mock helpers are `sh` scripts run via `sh -c`.
|
||||
#![cfg(unix)]
|
||||
|
||||
use xai_grok_test_support::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn provider_backed_model_sends_minted_token_on_the_wire() {
|
||||
let server = MockInferenceServer::start()
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let home = tempfile::TempDir::new().unwrap();
|
||||
|
||||
let grok_home = home.path().join(".grok");
|
||||
std::fs::create_dir_all(&grok_home).expect("create .grok home");
|
||||
|
||||
let counter = grok_home.join("mint-count");
|
||||
let helper = grok_home.join("mock-auth.sh");
|
||||
std::fs::write(
|
||||
&helper,
|
||||
format!(
|
||||
"#!/bin/sh\necho run >> {}\nprintf 'gateway-tok-1'\n",
|
||||
counter.display()
|
||||
),
|
||||
)
|
||||
.expect("write mock auth binary");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&helper, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
grok_home.join("config.toml"),
|
||||
format!(
|
||||
r#"[auth_provider.gateway]
|
||||
command = "{helper}"
|
||||
token_ttl_secs = 3600
|
||||
|
||||
[model.proxied-gateway]
|
||||
model = "mock-gateway-model"
|
||||
base_url = "{base}"
|
||||
context_window = 200000
|
||||
auth_provider = "gateway"
|
||||
"#,
|
||||
helper = helper.display(),
|
||||
// Already ends in `/v1`.
|
||||
base = server.url(),
|
||||
),
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
cmd.args([
|
||||
"-p",
|
||||
"say hi",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"proxied-gateway",
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--output-format",
|
||||
"json",
|
||||
])
|
||||
.arg("--cwd")
|
||||
.arg(workdir.path())
|
||||
.current_dir(workdir.path())
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
xai_grok_test_support::env::test_env_cmd_tokio(&mut cmd, &server.url(), home.path());
|
||||
// Don't attach to a developer's ambient leader; spawn fresh against the mock.
|
||||
cmd.env_remove("GROK_LEADER_SOCKET");
|
||||
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
assert_headless_success(&result, "auth provider e2e", Some(&server));
|
||||
|
||||
let runs = std::fs::read_to_string(&counter)
|
||||
.expect("helper must have run")
|
||||
.lines()
|
||||
.count();
|
||||
assert_eq!(runs, 1, "one turn mints exactly once");
|
||||
|
||||
let requests = server.requests();
|
||||
// The mock server plays both the first-party and the provider role on
|
||||
// one host, so the leak assertions are scoped to the inference path.
|
||||
let chat = requests
|
||||
.iter()
|
||||
.find(|e| e.method == "POST" && e.path.contains("chat/completions"))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"no POST /v1/chat/completions request logged; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
chat.authorization.as_deref(),
|
||||
Some("Bearer gateway-tok-1"),
|
||||
"the inference request must carry the minted provider token; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
);
|
||||
// `test-key-for-ci` is the harness's XAI_API_KEY; it stands in for the
|
||||
// session credential, which resolves below the provider arm.
|
||||
assert!(
|
||||
!requests.iter().any(|e| {
|
||||
e.path.contains("chat/completions")
|
||||
&& e.authorization
|
||||
.as_deref()
|
||||
.is_some_and(|a| a.contains("test-key-for-ci"))
|
||||
}),
|
||||
"the session credential must never reach the provider-backed endpoint; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
);
|
||||
}
|
||||
|
||||
/// A model that references an undefined `[auth_provider.<name>]` must fail
|
||||
/// closed end to end: the request goes out without a bearer (which the mock
|
||||
/// rejects), and the session credential is never substituted onto the wire.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn undefined_provider_fails_closed_and_never_leaks_session_key() {
|
||||
// Reject any bearer: nothing legitimate can satisfy this, since the model
|
||||
// references a provider that is never defined.
|
||||
let server = MockInferenceServer::start_with_required_auth(
|
||||
vec![MockModelEntry::new("mock-gateway-model")],
|
||||
"never-issued-token",
|
||||
)
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let home = tempfile::TempDir::new().unwrap();
|
||||
|
||||
let grok_home = home.path().join(".grok");
|
||||
std::fs::create_dir_all(&grok_home).expect("create .grok home");
|
||||
|
||||
// Model references `gateway`, but no `[auth_provider.gateway]` table exists.
|
||||
std::fs::write(
|
||||
grok_home.join("config.toml"),
|
||||
format!(
|
||||
r#"[model.proxied-gateway]
|
||||
model = "mock-gateway-model"
|
||||
base_url = "{base}"
|
||||
context_window = 200000
|
||||
auth_provider = "gateway"
|
||||
"#,
|
||||
base = server.url(),
|
||||
),
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
cmd.args([
|
||||
"-p",
|
||||
"say hi",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"proxied-gateway",
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--output-format",
|
||||
"json",
|
||||
])
|
||||
.arg("--cwd")
|
||||
.arg(workdir.path())
|
||||
.current_dir(workdir.path())
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
xai_grok_test_support::env::test_env_cmd_tokio(&mut cmd, &server.url(), home.path());
|
||||
cmd.env_remove("GROK_LEADER_SOCKET");
|
||||
|
||||
// The turn is expected to fail (the mock 401s the unauthenticated request);
|
||||
// we assert on the wire, not the exit code.
|
||||
let _ = run_headless_with_cmd(cmd).await;
|
||||
|
||||
let requests = server.requests();
|
||||
// Non-vacuity: the model was actually exercised.
|
||||
assert!(
|
||||
requests
|
||||
.iter()
|
||||
.any(|e| e.method == "POST" && e.path.contains("chat/completions")),
|
||||
"no chat request attempted; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
);
|
||||
assert!(
|
||||
!requests.iter().any(|e| {
|
||||
e.path.contains("chat/completions")
|
||||
&& e.authorization
|
||||
.as_deref()
|
||||
.is_some_and(|a| a.contains("test-key-for-ci"))
|
||||
}),
|
||||
"an undefined provider must fail closed, never sending the session \
|
||||
credential; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
);
|
||||
}
|
||||
|
||||
/// The documented `args` + JSON-output shape, end to end: a provider with
|
||||
/// `args = [...]` runs the helper directly (no shell) and parses a JSON
|
||||
/// `{access_token, expires_in}` payload, and the minted token reaches the wire.
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn provider_with_args_and_json_output_sends_minted_token() {
|
||||
let server = MockInferenceServer::start()
|
||||
.await
|
||||
.expect("start mock server");
|
||||
let workdir = git_workdir();
|
||||
let home = tempfile::TempDir::new().unwrap();
|
||||
|
||||
let grok_home = home.path().join(".grok");
|
||||
std::fs::create_dir_all(&grok_home).expect("create .grok home");
|
||||
|
||||
// The helper records the args it was invoked with (proving direct exec, no
|
||||
// shell) and prints a JSON token payload.
|
||||
let seen_args = grok_home.join("seen-args");
|
||||
let helper = grok_home.join("mock-auth-json.sh");
|
||||
std::fs::write(
|
||||
&helper,
|
||||
format!(
|
||||
"#!/bin/sh\nprintf '%s' \"$*\" > {}\n\
|
||||
printf '{{\"access_token\":\"gateway-tok-json\",\"expires_in\":3600}}'\n",
|
||||
seen_args.display()
|
||||
),
|
||||
)
|
||||
.expect("write mock auth binary");
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&helper, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
grok_home.join("config.toml"),
|
||||
format!(
|
||||
r#"[auth_provider.gateway]
|
||||
command = "{helper}"
|
||||
args = ["--profile", "corp"]
|
||||
token_ttl_secs = 3600
|
||||
timeout_secs = 10
|
||||
|
||||
[model.proxied-gateway]
|
||||
model = "mock-gateway-model"
|
||||
base_url = "{base}"
|
||||
context_window = 200000
|
||||
auth_provider = "gateway"
|
||||
"#,
|
||||
helper = helper.display(),
|
||||
base = server.url(),
|
||||
),
|
||||
)
|
||||
.expect("write config.toml");
|
||||
|
||||
let mut cmd = tokio::process::Command::new(grok_binary());
|
||||
cmd.args([
|
||||
"-p",
|
||||
"say hi",
|
||||
"--yolo",
|
||||
"--model",
|
||||
"proxied-gateway",
|
||||
"--max-turns",
|
||||
"1",
|
||||
"--output-format",
|
||||
"json",
|
||||
])
|
||||
.arg("--cwd")
|
||||
.arg(workdir.path())
|
||||
.current_dir(workdir.path())
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.kill_on_drop(true);
|
||||
xai_grok_test_support::env::test_env_cmd_tokio(&mut cmd, &server.url(), home.path());
|
||||
cmd.env_remove("GROK_LEADER_SOCKET");
|
||||
|
||||
let result = run_headless_with_cmd(cmd).await;
|
||||
assert_headless_success(&result, "auth provider args/json e2e", Some(&server));
|
||||
|
||||
let args = std::fs::read_to_string(&seen_args).expect("helper must have run");
|
||||
assert_eq!(
|
||||
args, "--profile corp",
|
||||
"args must be passed directly to the helper with no shell"
|
||||
);
|
||||
|
||||
let requests = server.requests();
|
||||
let chat = requests
|
||||
.iter()
|
||||
.find(|e| e.method == "POST" && e.path.contains("chat/completions"))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"no POST /v1/chat/completions request logged; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
chat.authorization.as_deref(),
|
||||
Some("Bearer gateway-tok-json"),
|
||||
"the JSON access_token must reach the wire; requests:\n{}",
|
||||
server.request_log_summary()
|
||||
);
|
||||
}
|
||||
|
|
@ -581,9 +581,10 @@ async fn test_runtime_profile_start_status_stop_across_clients() {
|
|||
return; // pprof can't start in sandbox — skip
|
||||
};
|
||||
assert!(matches!(
|
||||
started,
|
||||
ControlPayload::CpuProfileStarted { svg_path, .. } if svg_path == output_path
|
||||
));
|
||||
started,
|
||||
ControlPayload::CpuProfileStarted { svg_path, .. }
|
||||
if svg_path == output_path
|
||||
));
|
||||
|
||||
let status = client_b
|
||||
.send_control(ControlCommand::CpuProfileStatus)
|
||||
|
|
@ -595,15 +596,16 @@ async fn test_runtime_profile_start_status_stop_across_clients() {
|
|||
"registration should stay consistent with status behavior"
|
||||
);
|
||||
assert!(matches!(
|
||||
status,
|
||||
ControlPayload::CpuProfileStatus {
|
||||
active: true,
|
||||
stopping: false,
|
||||
svg_path: Some(path),
|
||||
frequency_hz: Some(200),
|
||||
..
|
||||
} if path == output_path
|
||||
));
|
||||
status,
|
||||
ControlPayload::CpuProfileStatus {
|
||||
active: true,
|
||||
stopping: false,
|
||||
svg_path: Some(path),
|
||||
frequency_hz: Some(200),
|
||||
..
|
||||
}
|
||||
if path == output_path
|
||||
));
|
||||
|
||||
let stopped = client_b
|
||||
.send_control(ControlCommand::StopCpuProfile)
|
||||
|
|
@ -611,9 +613,10 @@ async fn test_runtime_profile_start_status_stop_across_clients() {
|
|||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
stopped,
|
||||
ControlPayload::CpuProfileStopped { svg_path, .. } if svg_path == output_path
|
||||
));
|
||||
stopped,
|
||||
ControlPayload::CpuProfileStopped { svg_path, .. }
|
||||
if svg_path == output_path
|
||||
));
|
||||
assert!(output_path.exists());
|
||||
} else {
|
||||
let error = client_a
|
||||
|
|
@ -726,9 +729,10 @@ async fn test_runtime_profile_creates_missing_parent_directory_end_to_end() {
|
|||
return; // pprof can't start in sandbox — skip
|
||||
};
|
||||
assert!(matches!(
|
||||
started,
|
||||
ControlPayload::CpuProfileStarted { svg_path, .. } if svg_path == nested_output
|
||||
));
|
||||
started,
|
||||
ControlPayload::CpuProfileStarted { svg_path, .. }
|
||||
if svg_path == nested_output
|
||||
));
|
||||
|
||||
let stopped = client
|
||||
.send_control(ControlCommand::StopCpuProfile)
|
||||
|
|
@ -736,9 +740,10 @@ async fn test_runtime_profile_creates_missing_parent_directory_end_to_end() {
|
|||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(matches!(
|
||||
stopped,
|
||||
ControlPayload::CpuProfileStopped { svg_path, .. } if svg_path == nested_output
|
||||
));
|
||||
stopped,
|
||||
ControlPayload::CpuProfileStopped { svg_path, .. }
|
||||
if svg_path == nested_output
|
||||
));
|
||||
assert!(nested_output.exists());
|
||||
} else {
|
||||
let error = client
|
||||
|
|
|
|||
Loading…
Reference in a new issue