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
21 changes: 21 additions & 0 deletions scripts/run_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def arguments():
default="main.ucode.ci_e2e_anthropic_relay_mps",
help="Existing relayed (subscription-relay) Anthropic MPS for the hybrid-routing CUJ.",
)
parser.add_argument(
"--claude-provider-model",
default="claude-haiku-4-5-20251001",
help="Only model exposed by the Anthropic MPS discovery fixture.",
)
parser.add_argument(
"--codex-provider",
default="main.ucode.ci_openai_mps",
Expand All @@ -120,6 +125,16 @@ def arguments():
default="main.ucode",
help="Schema containing the dedicated model-discovery Model Services.",
)
parser.add_argument(
"--claude-parent-model",
default="main.ucode.ci_e2e_claude",
help="Claude-compatible Model Service in --parent-schema.",
)
parser.add_argument(
"--codex-parent-model",
default="main.ucode.ci_e2e_codex",
help="Codex-compatible Model Service in --parent-schema.",
)
parser.add_argument("--python", default=sys.executable, help="Python 3.12+ path or uv version.")
parser.add_argument("--dependency", action="append", default=[], metavar="PACKAGE==VERSION")
parser.add_argument("--constraints", type=Path, help="Replay a previous dependencies.txt.")
Expand Down Expand Up @@ -293,9 +308,12 @@ def run(command, *, cwd=output, env=base_env, timeout=600) -> str:
"codex_model": args.codex_model,
"claude_provider": args.claude_provider,
"claude_relayed_provider": args.claude_relayed_provider,
"claude_provider_model": args.claude_provider_model,
"codex_provider": args.codex_provider,
"codex_provider_model": args.codex_provider_model,
"parent_schema": args.parent_schema,
"claude_parent_model": args.claude_parent_model,
"codex_parent_model": args.codex_parent_model,
"dependencies": args.dependency,
"workspace": args.workspace,
},
Expand Down Expand Up @@ -534,9 +552,12 @@ def run(command, *, cwd=output, env=base_env, timeout=600) -> str:
"UG_INTEGRATION_CLAUDE_PROVIDER": args.claude_provider,
"UG_INTEGRATION_CLAUDE_RELAYED_PROVIDER": args.claude_relayed_provider,
"UG_INTEGRATION_CLAUDE_OAUTH_TOKEN": oauth_token,
"UG_INTEGRATION_CLAUDE_PROVIDER_MODEL": args.claude_provider_model,
"UG_INTEGRATION_CODEX_PROVIDER": args.codex_provider,
"UG_INTEGRATION_CODEX_PROVIDER_MODEL": args.codex_provider_model,
"UG_INTEGRATION_PARENT_SCHEMA": args.parent_schema,
"UG_INTEGRATION_CLAUDE_PARENT_MODEL": args.claude_parent_model,
"UG_INTEGRATION_CODEX_PARENT_MODEL": args.codex_parent_model,
"UCODE_TEST_WORKSPACE": args.workspace or "",
"DATABRICKS_BEARER": bearer,
}
Expand Down
37 changes: 25 additions & 12 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ All tests live directly in `integration/`; shared mechanics live in `utils/`.
| `test_ug_configure_codex_databricks` | Configure Databricks Hosted; execute the generated auth helper; open Codex TUI and read a file | Generated helper invokes `ug` with clean token stdout; completed assistant answer contains the file value; normal exit and reopen |
| `test_ug_configure_codex_openai_mps` | Select OpenAI MPS in the real configure picker; launch Codex | Saved provider in status; completed TUI file task; normal exit |
| `test_ug_claude_custom_oauth_cli_boots`, `test_ug_codex_custom_oauth_cli_boots` | Launch with `ENABLE_CUSTOM_OAUTH_FROM_CLI=1`, `--workspace`, and `--client-id databricks-cli` | Real TUI reaches a usable prompt, accepts keyboard input, exits normally, and saves `client_id = databricks-cli` in its generated CLI profile; Claude also reads the OS-managed settings and requires a profile-only `apiKeyHelper` |
| `test_case_13_configured_claude_reuses_saved_model_location` | Configure Claude with a model location, then launch without options | The saved parent supplies the discovered catalog |
| `test_case_14_configured_codex_reuses_saved_model_location` | Configure Codex with a model location, then launch without options | The saved parent supplies the discovered catalog |
| `test_case_15_fresh_claude_uses_system_models_when_discovery_disabled` | Launch fresh Claude with discovery disabled | Workspace `system.ai` models appear; Claude Code creates no cache after picker launch |
| `test_case_16_fresh_codex_uses_system_models_when_discovery_disabled` | Launch fresh Codex with discovery disabled | Workspace `system.ai` models appear without a scoped catalog |
| `test_case_17_*` | Launch configured and fresh Claude with a provider | Automatic discovery supplies exactly the provider catalog |
| `test_case_18_*` | Launch configured and fresh Codex with a provider | The provider supplies exactly its model catalog |
| `test_case_19_*` | Launch configured and fresh Claude with a model location | The explicit parent supplies exactly its picker catalog |
| `test_case_20_*` | Launch configured and fresh Codex with a model location | The explicit parent supplies exactly its model catalog |
| `test_case_21_*` | Launch configured and fresh Claude with a provider and discovery disabled | Claude uses native families; Claude Code creates no cache after picker launch |
| `test_case_22_*` | Launch configured and fresh Codex with a provider and discovery disabled | Codex uses its native catalog and ug writes no scoped catalog |
| `test_case_23_*` | Launch configured and fresh Claude with a parent and discovery disabled | Claude uses native families; Claude Code creates no cache after picker launch |
| `test_case_24_*` | Launch configured and fresh Codex with a parent and discovery disabled | Codex uses its native catalog and ug writes no scoped catalog |
| `test_ug_claude_headless_prompt_argument`, `test_ug_claude_headless_prompt_stdin`, `test_ug_claude_headless_prompt_after_separator` | Run Claude from a script using each prompt form | Structured final answer contains the file value; exit zero; no routing |
| `test_ug_codex_headless_prompt_argument`, `test_ug_codex_headless_prompt_stdin`, `test_ug_codex_headless_prompt_after_separator` | Run Codex from a script using each prompt form | Completed turn and final answer contain the file value; exit zero; no routing |
| `test_ug_claude_headless_explicit_model_bypasses_routing` | Pass `--model VALUE` / `--model=VALUE` with routing enabled | Real file task completes; no routing wrapper |
Expand All @@ -64,16 +76,16 @@ All tests live directly in `integration/`; shared mechanics live in `utils/`.
| `test_ug_and_ucode_auth_helpers_emit_only_the_supplied_bearer` | Run both auth helper commands with the public bearer override, with and without forced refresh | Exact token-only stdout, no warnings or ANSI escapes; no workspace authentication or saved state |
| `test_ug_and_ucode_web_search_helpers_preserve_mcp_stdio` | Initialize and list tools through both web-search helper commands | Exactly the MCP JSON-RPC responses; no text/ANSI contamination; existing server/tool identities preserved; no model request |

With both agents selected there are **42 live cases** (6 interactive TUI cases),
**4 managed-workspace cases** (marker `managed`, run against a separate workspace that
publishes a CodingAgentConfig), **28 managed-fixture cases** (marker `managed_fixture`), and
**5 installation checks**. The 12 numbered scenarios fetch the published config once per agent,
replace that agent's static model source with its dedicated test MPS, drop its incompatible static
defaults, and reuse the result across 24 explicit configured/fresh journeys. The other four
collected cases, from three test functions, inject focused model and MCP shapes. Parametrization
varies argument spelling or routing mode, never hides the
agent/provider in the test name. Duplicate boot-only cases are incorporated into the Databricks
configuration TUI journeys.
With both agents selected there are **62 live cases** (16 interactive TUI cases),
**4 un-stubbed managed-workspace cases** (marker `managed`), **28 managed-config fixture cases**
(marker `managed_fixture`), and **5 installation checks**. The 24 numbered scenarios comprise
**44 explicit journeys**, and all groups total **99 executions**. Cases 1–12 fetch the published
config once per agent, replace that agent's static model source with its dedicated test MPS, drop
its incompatible static defaults, and reuse the result across 24 configured/fresh journeys. The
other four managed-fixture executions inject focused model and MCP shapes. The un-stubbed cases
retain coverage of the config fetch/wire contract. Parametrization varies argument spelling or
routing mode, never hides the agent/provider in the test name. Duplicate boot-only cases are
incorporated into the Databricks configuration TUI journeys.
Generated-file cleanup and strict app-server stdout assertions remain enforced.

ug no longer runs a post-configure agent probe; the deprecated `--skip-validate`
Expand All @@ -94,7 +106,7 @@ dependency graph to reproduce a user's combination. Every relevant same-reposito
PR and push to `main` runs both smoke and the full CUJ suite. Smoke covers the
Databricks Hosted configure/TUI, custom OAuth CLI TUI, and headless argument
journeys for both agents, in two parallel jobs. After smoke finishes, the full
suite runs all 41 cases across two parallel agent jobs: one Claude VM and one
suite runs all 62 live cases across two parallel agent jobs: one Claude VM and one
Codex VM, each running its configure, headless, and commands/lifecycle cases
serially. Each agent is installed once for the full suite, and no two full jobs
for the same agent overlap within a run.
Expand Down Expand Up @@ -126,7 +138,8 @@ pending. The descriptive jobs provide the actual coverage and diagnostics.
| --- | --- |
| Live MCP and skills functionality | Deferred; installation tests cover the local web-search MCP handshake and tool listing, not upstream proxying or a real search request |
| Broad configure flags, tracing, multiple workspaces, and PAT flows | Deferred while focusing on basic CUJs |
| Provider switching, relayed/subscription MPS | Not covered by the four provider journeys |
| Relayed/subscription MPS discovery | Not covered by the scoped discovery journeys |
| Fresh provider/parent validation and mixed Bedrock filtering | Not covered after removing the duplicate model-discovery suites |
| TUI initial prompt supplied on the launch command line | Not yet covered; headless prompt arguments are covered |
| Follow-up turns and conversation resume | Not covered; reopen proves startup, not conversation resume |
| Claude/Codex interactive smart routing | Deferred at the user's request; routing jobs and live journeys removed. Unit/component routing tests remain, but do not establish live routing behavior. |
Expand Down
54 changes: 30 additions & 24 deletions tests/integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ test_ug_configure_claude_lifecycle.py # repeat setup, revert, rejected credent
test_ug_configure_codex_lifecycle.py # repeat setup, revert, rejected credentials
test_ug_claude_managed_model_discovery.py # fetched/reused Claude MPS policy cases
test_ug_codex_managed_model_discovery.py # fetched/reused Codex MPS policy cases
test_ug_claude_model_discovery.py # Tests-tab cases 13, 15, 17, 19, 21, 23
test_ug_codex_model_discovery.py # Tests-tab cases 14, 16, 18, 20, 22, 24
test_ug_configure_managed.py # managed workspace: static model list, no agent selector
test_ug_configure_managed_models.py # injected model lists: pickers and Codex fallback metadata
test_ug_configure_managed_mcp.py # injected managed MCP list
Expand Down Expand Up @@ -151,27 +153,31 @@ MPS CUJs select the existing services already used by e2e:
- Codex: `main.ucode.ci_openai_mps`, using its allowed `gpt-5-nano` model.

Use `--claude-provider` / `--claude-relayed-provider` / `--codex-provider` to
reproduce another existing service. Use `--codex-provider-model` when that OpenAI
service allows a different model. Those choices are recorded in `versions.json`.
reproduce another existing service. Use `--claude-provider-model` /
`--codex-provider-model` when it allows a different model. Those choices are recorded in `versions.json`.
No service is created or modified. A missing service, permission, or OAuth token
fails the selected CUJ, rather than skipping it.

There are **42 live cases** (including 6 TUI journeys) and **5 installation
checks** with both agents. A separate **4 managed-workspace cases** (one per agent,
an idempotent re-configure, and cache reuse within the TTL; marker `managed`) run against a
workspace that publishes a CodingAgentConfig; see "Managed-workspace journeys" below. A further
**28 `managed_fixture` cases** use `UCODE_MANAGED_CONFIG_STUB`: 24 explicit configured/fresh
managed-discovery journeys fetch the published config once per agent module, replace that agent's
static model source with its dedicated MPS, drop its incompatible static defaults, and reuse the
result; four collected cases from three test functions inject focused model/MCP shapes.
See the named coverage and gaps matrix in
Scoped discovery additionally requires Model Services
`main.ucode.ci_e2e_claude` and `main.ucode.ci_e2e_codex`. Override them with
`--parent-schema`, `--claude-parent-model`, or `--codex-parent-model`. The tests
consume but never create or modify them.

There are **62 live cases** (including 16 TUI journeys), **4 un-stubbed managed-workspace cases**
(marker `managed`), **28 managed-config fixture cases** (marker `managed_fixture`), and
**5 installation checks** with both agents. The 24 numbered scenarios comprise 44 explicit
journeys, and all groups total 99 executions. Cases 1–12 fetch the published config once per agent
module, replace that agent's static model source with its dedicated MPS, drop its incompatible
static defaults, and reuse it across 24 configured/fresh journeys. Four additional fixture
executions inject focused model/MCP shapes. The un-stubbed cases retain the config fetch/wire
contract; see "Managed-workspace journeys" below and the named coverage and gaps matrix in
[../README.md](../README.md).

```bash
# Append one of these selections to the runner command:
-- -m live # default: all live user journeys
-- -m smoke # six Hosted, custom OAuth CLI TUI, and headless journeys
-- -m tui # six interactive TUI journeys
-- -m 'live and tui' # sixteen interactive live configuration/model-discovery journeys
-- -k test_ug_codex_app_server_client_initializes # one named journey and its variants
# Use --installation-only before -- for package checks without credentials.
```
Expand Down Expand Up @@ -235,20 +241,20 @@ cannot receive those secrets.

The workspace check requires the secret to match
`https://eng-ml-inference-team-us-east-1.cloud.databricks.com` (a trailing slash
is accepted). It never changes the secret or switches workspaces. There is no CI
model-discovery or model-selection job. Real `ug configure` performs its normal
workspace discovery inside each test; only explicit-model scenarios choose and
record a discovered `system.ai` model as a test argument.
is accepted). It never changes the secret or switches workspaces. There is no
separate CI model-selection job; the full agent lanes include scoped model
discovery. Real `ug configure` performs its normal workspace discovery inside
each test; only explicit-model scenarios choose and record a discovered
`system.ai` model as a test argument.
Every same-repository PR and push to `main` runs **Smoke journeys**, followed by
**Full journeys** even if smoke fails. Smoke runs the Hosted configure/TUI,
headless argument, and custom OAuth CLI TUI journeys for each agent (six cases,
two agent jobs). Full runs all 42 live cases, including those smoke cases, in two
disjoint agent lanes:
**Full journeys** even if smoke fails. Smoke runs the Hosted configure/TUI, headless argument,
and custom OAuth CLI TUI journey for each agent (six cases, two agent jobs). Full runs all 62
live cases, including those smoke cases, in two disjoint agent lanes:

| Agent lane | Marker | Cases |
| --- | --- | --- |
| Claude | `live and claude` | 17 |
| Codex | `live and codex` | 25 |
| Claude | `live and claude` | 27 |
| Codex | `live and codex` | 35 |

Each lane installs only its agent CLI, once, and runs all its configure, headless,
commands, lifecycle, and applicable app-server journeys. Cases remain serial
Expand Down Expand Up @@ -329,7 +335,7 @@ The workflow consumes the stored bearer; it does not mint or refresh credentials
For a manual run, use **Actions → Integration → Run workflow**, select the branch,
and choose `full` (default), `smoke`, `tui`, or `installation`. `live` remains an
alias for `full`. Manual subsets are explicit: `smoke` runs just the six smoke
cases; `tui` adds `and tui` to each agent lane's marker and runs all six TUI cases. Installation
cases; `tui` adds `and tui` to each agent lane's marker and runs all 16 live TUI cases. Installation
checks always run. Set the ug/agent versions. From the CLI:

```bash
Expand Down Expand Up @@ -500,7 +506,7 @@ uv run --no-project --python 3.12 python scripts/run_integration.py \
unset DATABRICKS_BEARER
```

This runs all 42 live cases. For the five installation checks, run the same
This runs all 62 live cases. For the five installation checks, run the same
runner/version/index arguments with `--installation-only` and omit `-- -m live`;
no bearer or workspace is needed. Results remain under `.integration-runs/`.
Each invocation needs a new output directory; an existing one is rejected.
Expand Down
15 changes: 15 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ def claude_oauth_token():
return token


@pytest.fixture(scope="session")
def claude_provider_model():
return os.environ["UG_INTEGRATION_CLAUDE_PROVIDER_MODEL"]


@pytest.fixture(scope="session")
def codex_provider():
return os.environ["UG_INTEGRATION_CODEX_PROVIDER"]
Expand All @@ -119,3 +124,13 @@ def codex_provider_model():
@pytest.fixture(scope="session")
def parent_schema():
return os.environ["UG_INTEGRATION_PARENT_SCHEMA"]


@pytest.fixture(scope="session")
def claude_parent_model():
return os.environ["UG_INTEGRATION_CLAUDE_PARENT_MODEL"]


@pytest.fixture(scope="session")
def codex_parent_model():
return os.environ["UG_INTEGRATION_CODEX_PARENT_MODEL"]
Loading
Loading