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
57 changes: 57 additions & 0 deletions .github/workflows/oauth-scopes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: OAuth Scopes

# PRs use the release pinned in compose.yaml; nightly runs use the latest
# preview to catch server changes before they ship.
on:
pull_request:
# Code the probes run or import
Comment thread
EhabY marked this conversation as resolved.
paths:
- src/**
- test/mocks/**
- test/scopes/**
- package.json
- pnpm-lock.yaml
- vitest.config.mts
- .github/workflows/oauth-scopes.yaml
schedule:
# Off the hour, when GitHub delays or drops fewer scheduled runs
- cron: "17 6 * * *"
workflow_dispatch:

permissions:
contents: read

jobs:
live-server:
name: Live Server Test (coder-preview)
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: ./.github/actions/setup
- run: docker compose -f test/scopes/compose.yaml up -d --wait
env:
CODER_IMAGE: ${{ github.event_name == 'schedule' && 'ghcr.io/coder/coder-preview:latest' || '' }}
- run: pnpm test:scopes
env:
CODER_SCOPES_TEST_URL: http://localhost:7080
# A failed scheduled run notifies only one person, so track it in an issue.
- name: Report nightly failure
if: failure() && github.event_name == 'schedule'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
title="Nightly OAuth scope test failed"
issue=$(gh issue list --state open --search "\"$title\" in:title" --json number --jq '.[0].number')
if [ -n "$issue" ]; then
gh issue comment "$issue" --body "Failed again: $RUN_URL"
else
gh issue create --title "$title" --body "The scope test failed against \`coder-preview:latest\`: $RUN_URL"
fi
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Before changing anything under `packages/` or `test/webview/`, read
- Unit tests: `pnpm test`, or `pnpm test:extension` / `pnpm test:webview`
with an optional file path
- Integration tests: `pnpm test:integration`
- OAuth scope tests: `pnpm test:scopes`, against a live server
([docs/TESTING.md](docs/TESTING.md#oauth-scope-tests))
- Storybook: `pnpm storybook`; theme snapshots: `pnpm sync:vscode-themes`

Integration tests and the theme sync launch VS Code; on headless machines
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ guides cover the rest; they serve humans and coding agents alike, and
naming, and test placement.
- [Architecture](docs/ARCHITECTURE.md): how opening a workspace works, SSH
config management, Windows permissions and connection logging.
- [Testing](docs/TESTING.md): unit, webview and integration tests, and
testing the open flow by hand.
- [Testing](docs/TESTING.md): unit, webview, integration and OAuth scope
tests, and testing the open flow by hand.
- [Tooling](docs/TOOLING.md): linting, formatting, TypeScript, Node.js and
dependency upgrades.
- [Webviews](packages/AGENTS.md): React, `@repo/ui`, Storybook and the
Expand Down
21 changes: 21 additions & 0 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test/
├── unit/ # Extension unit tests (mirrors src/)
├── webview/ # Webview unit tests (mirrors packages/<pkg>/src/, jsdom)
├── integration/ # Integration tests (real VS Code, Mocha)
├── scopes/ # OAuth scopes against a live server
├── utils/ # Test utilities that are also tested
└── mocks/ # Shared test mocks
```
Expand All @@ -72,6 +73,26 @@ command with `xvfb-run -a`.
run in that copy with its own user data directory, so an open VS Code
doesn't interfere.

## OAuth scope tests

`test/scopes/` signs in and refreshes the token through the extension's OAuth
flow against a live server, then probes each server call the extension makes.
When `pnpm test` reports an unprobed method, add a probe to
`test/scopes/probes.ts` and run the suite. It needs Docker and a fresh
deployment, since it creates users and workspaces, and `pnpm test` leaves it
out:

```bash
docker compose -f test/scopes/compose.yaml up -d --wait
CODER_SCOPES_TEST_URL=http://localhost:7080 pnpm test:scopes
docker compose -f test/scopes/compose.yaml down -v # before the next run
```

Pull requests run against the Coder release pinned in `compose.yaml`, which
must enforce requested scopes, as 2.38 does and 2.37 doesn't. The
nightly run uses `coder-preview:latest` and reports failures in a "Nightly
OAuth scope test failed" issue. Set `CODER_IMAGE` to test another image.

## Testing the open flow by hand

There are a few ways you can test the "Open in VS Code" flow:
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
"storybook:build": "storybook build --config-dir .storybook",
"storybook:ci": "storybook build --test --config-dir .storybook",
"sync:vscode-themes": "node .storybook/themes/sync.mjs",
"test": "cross-env CI=true ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs",
"test": "cross-env CI=true ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --project !scopes",
"test:extension": "cross-env ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --project extension",
"test:integration": "pnpm build:test && node scripts/esbuild.mjs && vscode-test --config test/integration/.vscode-test.mjs",
"test:scopes": "cross-env ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --project scopes",
"test:webview": "cross-env ELECTRON_RUN_AS_NODE=1 electron node_modules/vitest/vitest.mjs --project webview",
"typecheck": "concurrently -g -n extension,tests,packages,storybook \"tsc --noEmit\" \"tsc --noEmit -p test\" \"pnpm typecheck:packages\" \"tsc --noEmit -p .storybook\"",
"typecheck:packages": "pnpm -r --filter \"./packages/*\" --parallel typecheck",
Expand Down
3 changes: 2 additions & 1 deletion pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ catalog:
react-dom: ^19.3.0
storybook: ^10.6.1
storybook-addon-pseudo-states: ^10.6.1
# typescript stays on the 6.0 API for typescript-eslint; TypeScript 7 runs `tsc`.
# typescript stays on the 6.0 API for typescript-eslint and the compiler API in
# test/unit/oauth/scopeProbes.test.ts; TypeScript 7 runs `tsc`.
# https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/
typescript: npm:@typescript/typescript6@^6.0.2
vite: ^8.3.2
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-electron.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ pnpm --dir "$RUNNER_DIR" add "electron@$ELECTRON_VERSION"

echo "Running tests with Electron $ELECTRON_VERSION..."
ELECTRON_RUN_AS_NODE=1 \
"$RUNNER_DIR/node_modules/.bin/electron" node_modules/vitest/vitest.mjs
"$RUNNER_DIR/node_modules/.bin/electron" node_modules/vitest/vitest.mjs --project "!scopes"
2 changes: 1 addition & 1 deletion src/websocket/oneWayWebSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export class OneWayWebSocket<
}
}

function rawDataToString(data: RawData): string {
export function rawDataToString(data: RawData): string {
if (Buffer.isBuffer(data)) {
return data.toString("utf8");
} else if (data instanceof ArrayBuffer) {
Expand Down
28 changes: 28 additions & 0 deletions test/scopes/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# A throwaway deployment for `pnpm test:scopes`; see docs/TESTING.md.
services:
postgres:
image: postgres:17
environment:
POSTGRES_USER: coder
POSTGRES_PASSWORD: coder
POSTGRES_DB: coder
healthcheck:
test: pg_isready -U coder
interval: 2s
retries: 30

coder:
image: ${CODER_IMAGE:-ghcr.io/coder/coder:v2.38.0@sha256:38a4cfc548b096484d6d1743e5f1a5af18301ed9a7ffde5150663822f9887026}
ports: ["127.0.0.1:7080:7080"]
environment:
CODER_PG_CONNECTION_URL: postgres://coder:coder@postgres:5432/coder?sslmode=disable
CODER_HTTP_ADDRESS: 0.0.0.0:7080
CODER_ACCESS_URL: http://localhost:7080
CODER_OAUTH2_PROVIDER_ENABLE: "true"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: curl -fs http://localhost:7080/healthz
interval: 2s
retries: 60
Loading
Loading