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
20 changes: 20 additions & 0 deletions .github/workflows/extension-contract-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Extension-Contract-Tests
on: [workflow_call]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: test/extension-contract-tests
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: "22"
cache: "npm"
cache-dependency-path: "test/extension-contract-tests/package-lock.json"
- run: npm ci
- run: npm test
- run: npm run check:conformance
4 changes: 4 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ jobs:
uses: ./.github/workflows/agent.yaml
vscode:
uses: ./.github/workflows/vscode.yaml
extension-contract-tests:
uses: ./.github/workflows/extension-contract-tests.yaml

connect-contract-tests:
uses: ./.github/workflows/connect-contract-tests.yaml
Expand Down Expand Up @@ -62,6 +64,7 @@ jobs:
[
agent,
vscode,
extension-contract-tests,
connect-contract-tests,
build,
package,
Expand Down Expand Up @@ -92,6 +95,7 @@ jobs:
[
agent,
vscode,
extension-contract-tests,
connect-contract-tests,
build,
package,
Expand Down
24 changes: 22 additions & 2 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,24 @@ jobs:
# Extensions
vscode:
uses: ./.github/workflows/vscode.yaml
extension-contract-tests:
uses: ./.github/workflows/extension-contract-tests.yaml

connect-contract-tests:
uses: ./.github/workflows/connect-contract-tests.yaml

# Slack notification on failure
slack-notification:
needs: [agent, build, package, e2e, vscode, connect-contract-tests]
needs:
[
agent,
build,
package,
e2e,
vscode,
extension-contract-tests,
connect-contract-tests,
]
if: failure()
runs-on: ubuntu-latest
steps:
Expand All @@ -51,7 +62,16 @@ jobs:

# Slack notification when nightly tests recover from failure
slack-notification-resolved:
needs: [agent, build, package, e2e, vscode, connect-contract-tests]
needs:
[
agent,
build,
package,
e2e,
vscode,
extension-contract-tests,
connect-contract-tests,
]
if: success()
runs-on: ubuntu-latest
steps:
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ jobs:
if: needs.detect-changes.outputs.has-code == 'true'
uses: ./.github/workflows/vscode.yaml

extension-contract-tests:
needs: detect-changes
if: needs.detect-changes.outputs.has-code == 'true'
uses: ./.github/workflows/extension-contract-tests.yaml

connect-contract-tests:
needs: detect-changes
if: needs.detect-changes.outputs.has-code == 'true'
Expand Down
16 changes: 16 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,22 @@ test *args=("-short ./..."):

go test {{ args }} -covermode set -coverprofile=cover.out

# Run extension contract tests (validates vscode/positron API usage)
test-extension-contracts:
#!/usr/bin/env bash
set -eou pipefail
{{ _with_debug }}

cd test/extension-contract-tests && npm test

# Check that vscode/positron mocks conform to real API type definitions
check-extension-contract-conformance:
#!/usr/bin/env bash
set -eou pipefail
{{ _with_debug }}

cd test/extension-contract-tests && npm run check:conformance

# Build the Connect API contract test harness binary
build-connect-harness:
go build -o test/connect-api-contracts/harness/harness ./test/connect-api-contracts/harness/
Expand Down
84 changes: 84 additions & 0 deletions test/extension-contract-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Extension Contract Tests

Contract tests that validate which **VSCode and Positron APIs** the extension calls, with what arguments, and what it expects back. These tests mock the `vscode` and `positron` modules, import actual extension code against those mocks, and assert API interactions.

## Architecture

```
Test code → Import real extension module → Mock vscode/positron APIs
(e.g., src/dialogs.ts) (vi.fn() spies record calls)
```

No Go binary, no HTTP server, no network. Tests run entirely in-process using Vitest with module aliasing to intercept `vscode` and `positron` imports.

## What's tested

Each test file captures the contract between extension code and the VSCode/Positron API surface:

| Test File | Extension Source | APIs Validated |
| ----------------------- | --------------------------- | ------------------------------------------------------------------------------------ |
| `positron-settings` | `utils/positronSettings.ts` | `workspace.getConfiguration("positron.r")` |
| `extension-settings` | `extension.ts` | `workspace.getConfiguration("positPublisher")` |
| `dialogs` | `dialogs.ts` | `window.showInformationMessage` (modal), `l10n.t` |
| `window-utils` | `utils/window.ts` | `window.showErrorMessage`, `withProgress`, `createTerminal` |
| `interpreter-discovery` | `utils/vscode.ts` | `commands.executeCommand`, `workspace.getConfiguration`, Positron runtime |
| `file-watchers` | `watchers.ts` | `workspace.createFileSystemWatcher`, `RelativePattern` |
| `llm-tools` | `llm/index.ts` | `lm.registerTool` |
| `open-connect` | `open_connect.ts` | `window.showInputBox`, `workspace.updateWorkspaceFolders`, `commands.executeCommand` |
| `auth-provider` | `authProvider.ts` | `authentication.registerAuthenticationProvider` |
| `connect-filesystem` | `connect_content_fs.ts` | `workspace.registerFileSystemProvider`, `FileSystemError` |
| `document-tracker` | `entrypointTracker.ts` | Editor/document change events, `commands.executeCommand("setContext")` |
| `activation` | `extension.ts` | `activate()` wiring: trust, URI handler, commands, contexts |

## Mock design

### `src/mocks/vscode.ts`

Comprehensive mock of the `vscode` module with `vi.fn()` spies for all APIs the extension uses. Includes constructors (`Disposable`, `EventEmitter`, `Uri`, `RelativePattern`, etc.), enums (`FileType`, `ProgressLocation`, etc.), and namespace objects (`commands`, `window`, `workspace`, `authentication`, `lm`, `l10n`).

### `src/mocks/positron.ts`

Mock of the `positron` module providing `acquirePositronApi()` and the `LanguageRuntimeMetadata` type.

### Conformance checks

`src/mocks/vscode.conformance.ts` and `src/mocks/positron.conformance.ts` are compile-time checks that verify every property in our mocks also exists in the real API types. If a mock includes a misspelled or removed API, `tsc` will produce a compile error. Run with `npm run check:conformance`.

### `src/helpers/`

Shared mock setup used by tests that import modules with many transitive dependencies (e.g., `extension.ts`). Imported via `import "../helpers/extension-mocks"`.

## Running

```bash
# Install dependencies (first time)
cd test/extension-contract-tests && npm install

# Run tests
just test-extension-contracts

# Or directly
cd test/extension-contract-tests && npm test

# Watch mode
cd test/extension-contract-tests && npm run test:watch

# Run conformance checks (verify mocks match real API types)
just check-extension-contract-conformance

# Or directly
cd test/extension-contract-tests && npm run check:conformance
```

## Adding a new contract test

1. Create a new file in `src/contracts/` following the naming convention: `<feature>.test.ts`
2. Import `vi` from `vitest` and the relevant `vscode` APIs from the mock
3. Mock any internal dependencies with `vi.mock("src/...")`
4. Import the extension module under test with `await import("src/...")`
5. Write tests that call extension functions and assert which VSCode APIs were invoked

## Related test suites

- Extension unit tests (`extensions/vscode/src/**/*.test.ts`) — Test internal logic
- E2E tests (`test/e2e/`) — Full integration with Docker
Loading
Loading