2026-07-18 12:04:25 +02:00
|
|
|
# Multi-App Acceptance Tests
|
2026-07-17 06:03:20 +02:00
|
|
|
|
2026-07-18 12:04:25 +02:00
|
|
|
This directory provides a reusable fixture and end-to-end coverage for composed
|
|
|
|
|
systems. The fixture deploys a two-node queue, a two-node result store, and a
|
|
|
|
|
worker process that consumes queued jobs and records their completion:
|
2026-07-17 06:03:20 +02:00
|
|
|
|
2026-07-18 12:04:25 +02:00
|
|
|
```text
|
|
|
|
|
workload -> queue -> worker -> result store -> expectation
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
The local happy-path test drives the fixture through a scenario:
|
2026-07-17 06:03:20 +02:00
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
let mut scenario = AppHost::scenario()
|
2026-07-18 12:04:25 +02:00
|
|
|
.with_app(JobStackApp::new())
|
|
|
|
|
.with_workload(EnqueueJobs::new(10))
|
|
|
|
|
.with_expectation(AllJobsCompleted::new(10))
|
2026-07-17 06:03:20 +02:00
|
|
|
.build()?;
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-18 12:04:25 +02:00
|
|
|
`multi-app-fixture` owns the stack definition. It deploys child apps, exposes
|
|
|
|
|
their typed handles, and returns a composed stack handle:
|
2026-07-17 06:03:20 +02:00
|
|
|
|
|
|
|
|
```rust
|
2026-07-18 12:04:25 +02:00
|
|
|
let queue = ctx.deploy_and_expose(self.queue).await?;
|
|
|
|
|
let results = ctx.deploy_and_expose(self.results).await?;
|
|
|
|
|
let worker = ctx
|
|
|
|
|
.deploy_and_expose(JobWorkerApp::new(queue_url, results_url))
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let stack = JobStackHandle { queue, results, worker };
|
2026-07-17 06:03:20 +02:00
|
|
|
ctx.expose(stack.clone())?;
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-18 12:04:25 +02:00
|
|
|
Workloads, expectations, and later lifecycle tests request the composed handle:
|
2026-07-17 06:03:20 +02:00
|
|
|
|
|
|
|
|
```rust
|
2026-07-18 12:04:25 +02:00
|
|
|
let stack = ctx.require_app::<JobStackHandle>()?;
|
2026-07-17 06:03:20 +02:00
|
|
|
```
|
|
|
|
|
|
2026-07-18 12:04:25 +02:00
|
|
|
The worker is part of the deployed system, rather than test code moving data
|
|
|
|
|
between otherwise unrelated applications. It lives in the
|
|
|
|
|
`multi-app-job-worker` crate, receives the queue and result-store endpoints from
|
|
|
|
|
the parent deployment, and is started through `LocalProcessApp`. Reverse
|
|
|
|
|
cleanup stops the worker before either dependency.
|
|
|
|
|
|
2026-07-17 06:03:20 +02:00
|
|
|
Resource lifecycle comes from the TF adapter used by a child deployment:
|
|
|
|
|
|
|
|
|
|
- `LocalProcessApp` manages one local binary process.
|
|
|
|
|
- `LocalAppCluster` manages a uniform local cluster.
|
|
|
|
|
|
|
|
|
|
Attached and external sources are not registered again at the app layer. The
|
|
|
|
|
outer scenario resolves them through its existing source providers, and the app
|
|
|
|
|
deployment receives the resulting node clients and control profile through
|
|
|
|
|
`DeployContext`. When the active deployer provides node control or cluster
|
|
|
|
|
readiness, the same handles are available through `DeployContext::node_control`
|
|
|
|
|
and `DeployContext::cluster_wait`; the app layer does not create replacements.
|
|
|
|
|
|
|
|
|
|
Managed adapters register cleanup during deployment. Cleanup runs in reverse
|
|
|
|
|
acquisition order and does not depend on the last handle clone being dropped.
|
|
|
|
|
An app-specific deployment only describes composition; it does not implement a
|
|
|
|
|
second lifecycle interface.
|
|
|
|
|
|
|
|
|
|
For a single uniform cluster, the core `ScenarioBuilder<AppEnv>` flow remains
|
|
|
|
|
valid. For composed systems, prefer this app-layer shape instead of building a
|
|
|
|
|
fake outer cluster or adding app-specific code to TF.
|
2026-07-18 12:04:25 +02:00
|
|
|
|
|
|
|
|
Run the local end-to-end test from the workspace root:
|
|
|
|
|
|
|
|
|
|
```shell
|
|
|
|
|
cargo test -p multi-app-e2e --test local_happy_path
|
|
|
|
|
```
|