Skip to content
Merged
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
1 change: 1 addition & 0 deletions .cursor/rules/sdk/commit-and-pr-format.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ globs:
- packages/rag/**
- packages/logging/**
- packages/error/**
- packages/test-suite/**
alwaysApply: false
---

Expand Down
2 changes: 2 additions & 0 deletions .cursor/rules/sdk/sdk-pod-packages.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The following packages belong to the SDK pod:
- `packages/logging`
- `packages/error`
- `packages/ai-sdk-provider`
- `packages/test-suite`
- `packages/inference`
- `packages/registry-server`
- `plugins/opencode` (tag slug: `opencode-plugin`)
Expand Down Expand Up @@ -44,6 +45,7 @@ globs:
- packages/logging/**
- packages/error/**
- packages/ai-sdk-provider/**
- packages/test-suite/**
- packages/inference/**
- packages/registry-server/**
- plugins/opencode/**
Expand Down
55 changes: 55 additions & 0 deletions .cursor/rules/sdk/test-suite/api-exports.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
description: Public API export structure for @qvac/test-suite
globs:
- packages/test-suite/src/index.ts
- packages/test-suite/src/mobile-runtime.ts
- packages/test-suite/src/types/*.ts
- packages/test-suite/src/schemas/*.ts
---

# Public API Export Rules

## Structure

- `types/` β€” TypeScript types + Zod schemas for config/test definitions
- `schemas/` β€” Zod schemas for MQTT messages and expectations
- `core/` β€” Runtime classes and helpers
- `utils/` β€” Utilities (config loader, validation helpers, MQTT connection)
- `mobile/` β€” React Native-only executors

## Entry points

- `.` β†’ `src/index.ts` (Node/desktop). Under the `react-native` condition this resolves to
`src/mobile-runtime.ts` instead.
- `./mobile` β†’ `src/mobile-runtime.ts`, a **reduced** surface: no CLI, no config loader, no esbuild. It adds
`AssetExecutor`, `buildMqttSessionOptions`, and `buildMqttSessionEndOptions`.
- `bin: qvac-test` β†’ `src/cli/index.ts`.

## Index Exports

`src/index.ts` re-exports the public API:

- **Types**: `QvacTestConfig`, `Expectation`, `TestDefinition`, `TestExecutor`, `TestResult`,
`TestHandler`, `TestExecutorConfig`, `TestDefinitions`, `ExtractTest`, `HandlerFn`, `QueueEmpty`,
`RegisterAck`, `TestPrepare`, `TestQueueItem`, `MqttConnectionConfig`, `CreateMqttClientOptions`,
`NodeMemoryPollerHandle`, `NodeMemoryPollerOptions`, `DesktopMemoryPollerHandle`,
`DesktopMemoryPollerOptions`
- **Schemas**: `expectationSchema`, `mqttConnectionSchema`, `testDefinitionSchema`,
`qvacTestConfigSchema`, `consumerRegistrationSchema`, `testRequestSchema`, `testPrepareSchema`,
`testStartSchema`, `testReloadSchema`, `testResultSchema`, `heartbeatSchema`, `queueEmptySchema`,
`batchCompleteSchema`, `testQueueItemSchema`, `registerAckSchema`, `testAssignmentSchema`
- **Core**: `BatchOrchestrator`, `ConsumerBase`, `BaseExecutor`, `SkipExecutor`, `defineTests`,
`createExecutor`, `startNodeMemoryPoller`, `startDesktopMemoryPoller`
- **Helpers**: `defineConfig`
- **Utils**: `ValidationHelpers`, `chainExpectation`, `findConfig`, `loadConfig`, `loadTests`,
`createMqttClient`, `buildMqttOptions`, `buildMqttConnectionConfig`, `logMqttConnectionSecurity`

Adding or removing anything here is an `[api]` (or `[bc]`) change β€” see `commit-and-pr-format.mdc`.

## Adding Expectation Types

1. Add schema in `src/schemas/expectations.ts`
2. Add to the `expectationSchema` union
3. Update `ValidationHelpers` in `utils/validation-helpers.ts`

Current types: `contains-all`, `contains-any`, `regex`, `numeric-range`, `type`, `throws-error`, `function`
162 changes: 162 additions & 0 deletions .cursor/rules/sdk/test-suite/architecture.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
description: Framework architecture reference for @qvac/test-suite
globs:
- packages/test-suite/**/*.ts
- packages/test-suite/**/*.tsx
---

# Framework Architecture Reference

## Core Interfaces

### TestDefinition
```typescript
{
testId: string;
params: any;
expectation: Expectation;
metadata?: Record<string, any>;
suites?: string[];
skip?: { reason: string; issue?: string; impact?: string; platforms?: string[] };
}
```

### TestHandler
```typescript
{
pattern: RegExp;
setup?(testId, context): Promise<void>;
execute(testId, context, params, expectation): Promise<TestResult>;
teardown?(testId, context): Promise<void>;
}
```

### QvacTestConfig
```typescript
{
mqtt?: {
brokerUrl?: string | { env: string };
broker?: { protocol, host, port, path };
username?: string | { env: string };
password?: string | { env: string };
caPath?: string | { env: string };
certPath?: string | { env: string };
keyPath?: string | { env: string };
sessionExpiryInterval?: number;
rejectUnauthorized?: boolean;
};
testDir: string;
consumers: {
shared?: { include },
desktop?: { platforms, entry, include, dependencies },
electron?: { platforms, entry, appDir, appName, include, dependencies,
packageManager, packageScript },
snap?: { runtime, entry, appDir, snapName, appCommand, artifactPath,
snapConfigDir, packageManager, packageScript },
mobile?: { platforms, entry, include, dependencies,
mobileInit?, metroConfig?, qvacConfig?, assets?, expoPlugins?, copyArtifact? }
};
comparison?: { baselineRef };
}
```

String/number config fields support `{ env: "VAR_NAME" }` to read from environment variables at runtime.
`.env` is loaded automatically before config resolution.

## MQTT Topics

```
qvac/register # Consumer β†’ Producer: registration
qvac/register-ack/{id} # Producer β†’ Consumer: ack with the complete test queue
qvac/test-prepare # Consumer β†’ Producer: test setup is about to begin
qvac/test-start # Consumer β†’ Producer: test started
qvac/results # Consumer β†’ Producer: test result
qvac/heartbeat # Consumer β†’ Producer: keepalive
qvac/queue-empty # Consumer β†’ Producer: an empty queue finished bootstrap
qvac/profiling # Consumer β†’ Producer: profiler export data
qvac/app-memory # Consumer β†’ Producer: runtime memory samples
qvac/batch-complete # Producer β†’ All: batch done
```

The first consumer registered for a run owns its queue. Additional consumers receive an empty queue
and are not tracked as run participants.

## Consumer Build Process

### Desktop
```
1. Read config (include, dependencies)
2. Create temp directory
3. Install dependencies from package.json β†’ temp/node_modules/
4. Bundle entry + includes with esbuild
- External: keep node_modules as requires
5. Output: build/consumers/{platform}/
β”œβ”€β”€ consumer.js
└── node_modules/
```

### Mobile
```
1. Create temp Expo project
2. Install dependencies (RN-compatible versions)
3. Copy source + tests to project
4. Metro bundles (includes node_modules)
5. Expo build β†’ IPA/APK
```

Templates in `templates/mobile-consumer/` are copied verbatim into the generated app, so their import
specifier for the framework is rewritten at scaffold time to whichever package name the consumer actually
installed (`resolveFrameworkPackageName` in `src/cli/commands/build-consumer-mobile.ts`).

## RunID

Generated from a timestamp, per entry point:

| Entry point | Format |
| --- | --- |
| `run:local:*` | `local-<epoch-ms>` (`generateRunId`, `cli/utils/process-manager.ts`) |
| `run:producer` | `run-<epoch-ms>` |
| `build:consumer:{android,ios}` | `mobile-<epoch-ms>` |
| `run:bootstrap:snap` | `snap-bootstrap-<epoch-ms>` |

`--runId` overrides all of these, and CI always passes one explicitly.

> `qvacTestConfigSchema` still declares a `runIdStrategy` field described as generating
> `repo-branch-commit-timestamp`. **Nothing reads that field** and no such format is ever produced. Treat
> the table above as authoritative; the schema entry is inert and should be removed or implemented in a
> separate change.

## Validation Types (Built-in)

- `contains-all`: All strings present
- `contains-any`: At least one string present
- `regex`: Pattern match
- `numeric-range`: min/max bounds
- `type`: Type checking (`string`, `number`, `array`)
- `throws-error`: Error contains text
- `function`: Custom validator function (not serializable over MQTT)

## Report Formats

### JSON (`results-{runId}.json`)
```json
{
"runId": "...",
"timestamp": "...",
"summary": { "total": 0, "passed": 0, "failed": 0, "skipped": 0, "successRate": "0.0", "duration": 0 },
"categories": { "category": { "passed": 0, "failed": 0, "skipped": 0, "total": 0 } },
"suites": { "suite": { "passed": 0, "failed": 0, "skipped": 0, "total": 0 } },
"tests": [{ "testId": "", "consumerId": "", "outcome": "", "duration": 0 }],
"consumers": [{ "consumerId": "", "platform": "" }],
"system": { "hostname": "", "platform": "", "nodeVersion": "" },
"profiling": [],
"memory": []
}
```

### HTML (`report-{runId}.html`)
- Per-category results with pass/fail/skip counts
- Per-suite breakdown (when tests have suite tags)
- Failed test details with error output
- Profiling data and memory charts (when available)
- System information
48 changes: 48 additions & 0 deletions .cursor/rules/sdk/test-suite/main.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
description: Core context for the @qvac/test-suite package (packages/test-suite)
globs:
- packages/test-suite/**
alwaysApply: false
---

# QVAC Test Suite

`packages/test-suite` is the `@qvac/test-suite` package: a distributed MQTT test-orchestration framework
(`qvac-test` CLI) that runs a consumer's test definitions across desktop Node, packaged Electron, strict
Snap, iOS, and Android. It is an SDK-pod package β€” see `sdk-pod-packages.mdc`.

- The framework is the **harness**, not a suite of tests. It ships no tests of its own. The tests it runs
live in the consumer repo; in this monorepo that consumer is `packages/sdk/e2e`.
- Protect the public surfaces: CLI behaviour, package `exports`, config/runtime contracts, mobile
templates, the producer/consumer/report flow, and publish parity with the other SDK-pod packages.
- `dist/` is gitignored and built by `npm run build`. Never commit build output, and never assume a fresh
checkout has one β€” CI packs the package when a consumer points at it via a `file:` link.

## Names

Published as `@qvac/test-suite` (public npm) and `@tetherto/test-suite-mono` (GitHub Packages, via the
`name-suffix: "-mono"` the monorepo GPR action applies).

Renamed in 0.11.0 from `@qvac/qvac-test-suite` / `@tetherto/qvac-test-suite`. Both old names are still
recognised at runtime β€” see `FRAMEWORK_PACKAGE_NAMES` in `src/cli/commands/build-consumer-mobile.ts` and
the esbuild `external` list in `src/utils/test-loader.ts`. Keep all four names in those two lists until the
deprecated package is retired.

## Local development

```bash
cd packages/test-suite
npm install
npm run check # lunte + eslint + prettier
npm run build # tsc
```

## CI

`SDK Pod Checks` gates this package: `format`, `lint`, `typecheck` and `build` run on every PR touching
it. `trigger-reusable-lib-test-suite.yml` publishes it β€” GPR dev builds from `main`, `feature-*` and
`tmp-*`, public npm from `release-test-suite-*`. Because the package ships only compiled output and has
no `prepare` script, `dist` is built once in a dedicated job and downloaded by each publish job.

The SDK e2e consumer wiring β€” the `test-suite-source` selector on `test-sdk.yml` β€” lands with the
consumer migration, not here.
1 change: 1 addition & 0 deletions .cursor/skills/_lib/pr-skills/pr-test-sdk.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const SDK_POD_PACKAGE_PATHS = new Set([
"packages/rag",
"packages/logging",
"packages/error",
"packages/test-suite",
]);

const TOKEN_STOP_WORDS = new Set([
Expand Down
4 changes: 4 additions & 0 deletions .github/sdk-pod-checks.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@
"path": "packages/ai-sdk-provider",
"pkg_manager": "bun"
},
{
"package": "test-suite",
"path": "packages/test-suite"
},
{
"package": "opencode-plugin",
"path": "plugins/opencode"
Expand Down
1 change: 1 addition & 0 deletions .github/teams/sdk.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"packages/logging/",
"packages/error/",
"packages/ai-sdk-provider/",
"packages/test-suite/",
"packages/inference/",
"packages/registry-server/",
"plugins/opencode/",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-release-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
run: |
set -euo pipefail
base_ref="${{ github.base_ref }}"
pods=" sdk cli rag logging error ai-sdk-provider "
pods=" sdk cli rag logging error ai-sdk-provider test-suite "
if [[ ! "$base_ref" =~ ^release-(.+)-([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
echo "::notice::Base '$base_ref' is not release-<slug>-x.y.z β€” skipping SDK-pod release guard"
echo "is_sdk_pod_package=false" >> "$GITHUB_OUTPUT"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pr-validation-sdk-pod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
- "packages/logging/**"
- "packages/error/**"
- "packages/ai-sdk-provider/**"
- "packages/test-suite/**"
branches:
- main
- release-*
Expand Down
Loading
Loading