Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/e2e-cucumber/features/automations.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Feature: Automation watchers

# `rocm automations enable/disable <watcher> --mode <mode>` toggles a built-in
# automation watcher in the CLI config and prints a confirmation. This feature
# pins only the enable/disable/mode-confirmation slice verified during the
# walkthrough; the broader automation behaviour is covered elsewhere. Config-only
# (no GPU, no network), so it runs on the mock lane every PR.

@id:automations-enable-confirms-mode
Scenario: 1 - Enabling a watcher confirms its mode
Given a fresh CLI configuration
When the user enables an automation watcher in observe mode
Then the CLI confirms the watcher is enabled in observe mode
When the user re-enables the same watcher in propose mode
Then the CLI confirms the watcher is enabled in propose mode

@id:automations-disable-confirmed
Scenario: 2 - Disabling a watcher is confirmed
Given an enabled automation watcher
When the user disables the watcher
Then the CLI confirms the watcher is disabled

@id:automations-enable-unknown-refused
Scenario: 3 - Enabling an unknown watcher is refused
Given a fresh CLI configuration
When the user tries to enable a watcher that does not exist
Then the CLI refuses and names it as unknown
69 changes: 69 additions & 0 deletions tests/e2e-cucumber/features/config.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
Feature: Configuration mutations

# `rocm config <...>` edits the CLI's own config file (JSON under the isolated
# config dir) and prints a one-line confirmation. These behaviours were verified
# correct during the walkthrough but had no scenario protecting them. They touch
# only the config file — no GPU, no network — so they run on the mock lane every
# PR, catching a regression in the config surface before the field does.

@id:config-default-engine-set-and-cleared
Scenario: 1 - Setting and clearing the default engine is confirmed
Given a fresh CLI configuration
When the user sets the default engine
Then the CLI confirms the default engine was set
When the user clears the default engine
Then the CLI confirms the default engine was cleared

@id:config-default-runtime-set-and-cleared
Scenario: 2 - Setting and clearing the default runtime is confirmed
Given a fresh CLI configuration
When the user sets the default runtime
Then the CLI confirms the default runtime was set
When the user clears the default runtime
Then the CLI confirms the default runtime was cleared

@id:config-telemetry-mode-confirmed
Scenario: 3 - Choosing a telemetry mode is confirmed with its policy
Given a fresh CLI configuration
When the user turns telemetry off
Then the CLI confirms the telemetry mode and states the policy

@id:config-permissions-mode-confirmed
Scenario: 4 - Choosing a permissions mode is confirmed
Given a fresh CLI configuration
When the user selects a permissions mode
Then the CLI confirms the permissions mode

@id:config-set-engine-requires-target
Scenario: 5 - Configuring an engine requires a target
Given a fresh CLI configuration
When the user configures an engine without saying what to change
Then the CLI refuses and explains a target is required
When the user configures an engine with a runtime to use
Then the CLI confirms the engine configuration was updated

@id:config-provider-enable-disable
Scenario: 6 - Enabling and disabling a cloud provider is confirmed
Given a fresh CLI configuration
When the user enables a cloud provider
Then the CLI confirms the provider is enabled for prompt sending
When the user disables that provider
Then the CLI confirms the provider is disabled

@id:config-local-provider-not-toggleable
Scenario: 7 - The always-on local provider cannot be toggled as a cloud provider
Given a fresh CLI configuration
When the user tries to enable the local provider
Then the CLI refuses and explains the local provider is always enabled

# @requires-os:linux: the premise is a secure store that cannot save. The Linux
# Secret Service is reached over D-Bus, which the step forces unreachable so the
# save deterministically fails; the Windows/macOS credential stores are always
# present and cannot be disabled the same way, so the failure premise only holds
# on Linux. The no-echo property it verifies is the security-relevant contract.
@id:config-provider-key-no-secret-storage @requires-os:linux
Scenario: 8 - Saving a provider key without secure storage fails without leaking the key
Given a machine with no secure secret storage
When the user saves a provider API key
Then the CLI reports it could not save the key securely
And the key value never appears in the output
26 changes: 26 additions & 0 deletions tests/e2e-cucumber/features/logs.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Feature: Log inspection

# `rocm logs` reads the CLI's own recorded command logs from the isolated data
# dir and can filter them by a search term or a service. These behaviours were
# verified correct during the walkthrough but had no scenario. They read planted
# log files only — no GPU, no network — so they run on the mock lane every PR.
# The search count is asserted by the number of MATCHING lines (deterministic),
# not the recent-line total, which also counts other log sources.

@id:logs-search-reports-match-count
Scenario: 1 - Searching logs reports how many recent lines match
Given recorded command logs containing several lines about a topic
When the user searches the logs for that topic
Then the CLI reports the matching recent lines

@id:logs-search-absent-term-no-matches
Scenario: 2 - Searching logs for an absent term reports no matches
Given recorded command logs containing several lines about a topic
When the user searches the logs for a term that appears nowhere
Then the CLI reports no matching lines

@id:logs-service-and-search-conflict
Scenario: 3 - Asking for a service and a search term at once is refused
Given recorded command logs containing several lines about a topic
When the user asks for one service's logs and a search term together
Then the CLI refuses and explains only one may be used
10 changes: 10 additions & 0 deletions tests/e2e-cucumber/features/model_serving.feature
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,13 @@ Feature: Model serving
When the user serves a model pinned to a GPU index that does not exist
Then serving is refused before any engine starts
And the user is told that GPU index is unavailable

# A runtime and an environment are two ways to pick what a serve runs against,
# and choosing both at once is ambiguous, so the CLI rejects the combination
# during argument parsing — before any engine or GPU work. No device needed, so
# this runs on the mock lane every PR.
@id:serve-runtime-and-env-selectors-conflict
Scenario: 15 - Selecting both a runtime and an environment at once is refused
When the user serves a model selecting both a runtime and an environment
Then serving is refused before any engine starts
And the user is told the two selectors cannot be combined
11 changes: 11 additions & 0 deletions tests/e2e-cucumber/features/networking.feature
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ Feature: Native HTTP networking
And the model is registered with the CLI
When the user sends a one-shot chat prompt through the CLI
Then the CLI prints the assistant's reply

# The EAI-7409 public-bind contract. Binding the server to a non-loopback
# interface exposes it to the network, so the CLI refuses unless the user
# explicitly opts in with `--allow-public-bind`. This check is pre-flight — the
# first thing `serve` does, before any engine or model work — so it needs no GPU
# and runs on the mock lane every PR.
@id:networking-public-bind-requires-opt-in
Scenario: 3 - Binding to a public interface without opt-in is refused up front
When the user serves a model bound to a public interface without allowing public binding
Then serving is refused before any engine starts
And the user is told to allow public binding first
39 changes: 39 additions & 0 deletions tests/e2e-cucumber/features/runtime_lifecycle.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Feature: Runtime lifecycle state machine

# `rocm runtimes activate/rollback/uninstall/import` move a runtime through its
# registry state machine. Only install/adopt/list were covered before; the
# activate/rollback/uninstall/import transitions were verified during the
# walkthrough but unprotected. The scenarios plant read-only (externally-sourced)
# runtimes in the isolated registry, so no SDK download or GPU is needed — they
# run on the mock lane every PR. Related EAI-7404.

@id:runtime-activate-records-previous
Scenario: 1 - Activating a runtime records where it changed from
Given two registered runtimes and none active
When the user activates the first runtime
Then that runtime becomes active having changed from nothing
When the user activates the second runtime
Then that runtime becomes active having changed from the first

@id:runtime-rollback-returns-to-previous
Scenario: 2 - Rolling back returns to the previously active runtime
Given two registered runtimes with the second active after the first
When the user rolls back
Then the first runtime is active again

@id:runtime-uninstall-keeps-external-folder
Scenario: 3 - Uninstalling an externally-sourced runtime keeps its folder
Given a registered read-only runtime
When the user uninstalls that runtime
Then its registry entry is removed
And its external folder is left in place

@id:runtime-import-rejects-duplicate-unless-replacing
Scenario: 4 - Importing a runtime, then rejecting a duplicate unless replacing
Given a runtime manifest to import
When the user imports the runtime
Then the runtime is registered as read-only
When the user imports the same runtime again
Then the CLI refuses because it already exists
When the user imports it again allowing replacement
Then the import succeeds
14 changes: 14 additions & 0 deletions tests/e2e-cucumber/features/update.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Feature: Update report

# `rocm update` (with no arguments) prints an update report: which managed
# runtimes have updates, plus the status of each update feed (CLI, engines,
# model recipes, runtimes). The walkthrough verified it correctly distinguishes
# published feeds from not-configured ones, but nothing pinned it. Run with no
# managed runtimes so the report needs no network — mock lane, every PR.

@id:update-report-distinguishes-feed-status
Scenario: 1 - The update report distinguishes configured from not-configured feeds
Given a machine with no managed runtimes
When the user checks for updates
Then the report shows there are no managed runtimes to update
And it reports each update feed's status, marking unpublished feeds as not configured
55 changes: 55 additions & 0 deletions tests/e2e-cucumber/tests/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,21 @@ use tempfile::TempDir;

mod e2e {
pub mod artifact_steps;
pub mod automations_steps;
pub mod bench_steps;
pub mod chat_steps;
pub mod config_steps;
pub mod dash_steps;
pub mod diagnose_steps;
pub mod engines_steps;
pub mod examine_steps;
pub mod lifecycle_steps;
pub mod logs_steps;
pub mod runtime_lifecycle_steps;
pub mod runtime_steps;
pub mod serving_steps;
pub mod tui_driver;
pub mod update_steps;
}

// ── World ──────────────────────────────────────────────────────────
Expand Down Expand Up @@ -517,6 +522,56 @@ pub fn run_rocm_ok(world: &E2eWorld, args: &[&str]) -> String {
stdout
}

/// Like [`run_rocm`], but writes `stdin` to the child's standard input and sets
/// extra environment variables on the child.
///
/// Used by scenarios that drive a command reading from stdin — e.g. `config
/// set-provider-key`, which reads the secret from stdin non-interactively. The
/// scenario can then assert on both the exit code and that the piped secret is
/// never echoed back. `envs` lets a scenario also control the child's environment
/// (e.g. point the secret store at an unreachable D-Bus so the save deterministically
/// fails), applied on top of the scenario's isolated config/data/cache env.
pub fn run_rocm_with_stdin(
world: &E2eWorld,
args: &[&str],
stdin: &str,
envs: &[(&str, &str)],
) -> (String, String, i32) {
use std::io::Write as _;
use std::process::Stdio;

let binary = rocm_binary();
let mut cmd = std::process::Command::new(&binary);
cmd.args(args);
world.isolate_cmd(&mut cmd);
for (key, value) in envs {
cmd.env(key, value);
}
cmd.stdin(Stdio::piped());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd
.spawn()
.unwrap_or_else(|e| panic!("failed to run {binary}: {e}"));
child
.stdin
.take()
.expect("child stdin was not piped")
.write_all(stdin.as_bytes())
.expect("failed to write to child stdin");
let output = child
.wait_with_output()
.unwrap_or_else(|e| panic!("failed to wait on {binary}: {e}"));
let rc = output.status.code().unwrap_or(-1);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
record_command(world.current_scenario.as_deref(), args, rc, &stdout);
(
stdout,
String::from_utf8_lossy(&output.stderr).to_string(),
rc,
)
}

/// Like [`run_rocm`], but with extra environment variables set on the child.
///
/// Used by scenarios that must control the device environment the CLI and engine
Expand Down
Loading
Loading