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: 10 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,38 @@ name: Test specification
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * *'
- cron: "0 0 * * *"
jobs:
generic:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- uses: pnpm/action-setup@v4
- uses: pnpm/action-setup@v6
name: Install pnpm
with:
version: 8
version: 11
run_install: false

- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: 20
cache: 'pnpm'
node-version: 24
cache: "pnpm"

- name: Install dependencies
run: |
pnpm install
pnpm ci
sudo apt-get install -y jq

- name: Test
run: |
curl -L -o openapi.yaml "$(curl -s https://api.github.com/repos/vrchatapi/specification/releases \
| jq -r '.[] | select(.prerelease) | .assets[] | select(.name=="openapi-internal+legacy.yaml").browser_download_url' \
| head -n 1)"


rm data/state -rf

./node_modules/.bin/ava reset-cache
Expand Down
40 changes: 40 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Run tests via PNPM",
"type": "node",
"request": "launch",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"test"
],
"skipFiles": [
"<node_internals>/**"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
},
{
"name": "Run fast-fail tests via PNPM",
"type": "node",
"request": "launch",
"runtimeExecutable": "pnpm",
"runtimeArgs": [
"run",
"fast-fail"
],
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**",
"**/dist/pnpm.mjs"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
}
]
}
63 changes: 63 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Agent instructions for this project

## Package manager
- Use `pnpm` for all package management

## Setup and Commands

### Initial Setup Checklist
1. Create a `.env` file in the project root (loaded via `dotenv/config`)
2. Verify all required environment variables are set and non-empty:
- `VRCHAT_EMAIL`
- `VRCHAT_USERNAME`
- `VRCHAT_PASSWORD`
- `VRCHAT_TOTP_SECRET`
- `VRCHAT_FRIEND_ID`
- `VRCHAT_GROUP_ID`
- **If any variable is missing, empty, or malformed, stop immediately and print the exact variable names that must be set before any test run.**
3. Before every local test run, execute `pnpm test:clean` first. Do not reuse any existing `data/state/` or AVA cache from a previous run. (CI does this automatically.)
4. Execute `pnpm get-spec` to download the latest `openapi-internal+legacy.yaml` to `openapi.yaml` (CI does this automatically).

### Available Commands
- `pnpm test` — run all tests (AVA; serial mode configured in `ava.config.js`)
- `pnpm fast-fail` — stop on first failure
- `pnpm type-check` — TypeScript type check
- `pnpm test:clean` — clear `data/state` and AVA cache (`pwsh ./scripts/clean.ps1`)
- `pnpm get-spec` — download latest `openapi-internal+legacy.yaml` to `openapi.yaml`

## Project purpose
Integration-test suite against the live VRChat API. Downloads the latest `openapi-internal+legacy.yaml` from vrchatapi/specification releases, then validates actual API responses against the schema.

## Test structure
- Test files in `tests/*.ts`, run in the order defined by `tests/_order.json`: authentication → users → worlds → instances → avatars (then alphabetical)
- All tests are serial (`ava --serial`)
- Tests share state via `data/state/` (files on disk — cookies, session data, cached values)
- Test results recorded as markdown to `data/requests/`

## Running tests

### Authentication and Error Handling
- Auth flow: login with Basic auth → 2FA TOTP → verify → cookie-based reuse
- Rate-limit protection: 200ms between requests, exponential backoff up to 10 retries on 429
- **If login fails, TOTP verification fails, or cookie-based reuse cannot be established, abort the run immediately with a clear error message and do not continue with partial state.**

## Test patterns
- `testOperation` macro (from `_utilities.ts`) handles: parameter injection, schema validation (`@exodus/schemasafe` lax mode), response logging
- `unstable: true | string[]` — marks response values that change every run (timestamps, versions, etc.)
- `sensitive: true` — redacts the entire response body
- `test.todo(...)` — placeholder for unimplemented endpoints
- Use `test.before(failUnauthenticated)` in files that need a logged-in user

## Key files
- `tests/_utilities.ts` — core test framework (fetch, schema validation, request/response recorder)
- `tests/_consts.ts` — env vars, shared constants like `tupperUserId`, `defaultAvatarId`
- `tests/_cache.ts` — file-based state and caching, sensitive/unstable value tracking
- `tests/_users.ts` — shared unstable key lists (e.g. `unstableUserKeys`)
- `ava.config.js` — AVA config with serial mode, custom test ordering via `_order.json`, tsx loader
- `scripts/clean.ps1` — clears `data/state/` and AVA cache for a fresh run (`pnpm test:clean`)
- `scripts/get-spec.ps1` — downloads latest `openapi-internal+legacy.yaml` to `openapi.yaml`
- `openapi.yaml` (in `.gitignore`) — downloaded at test time by CI or `scripts/get-spec.ps1`
- `data/` — ignored except for `data/requests/` (test output committed by CI)

## CI
GitHub Actions runs daily (or manual dispatch). Downloads spec via curl+jq, clears `data/state/`, runs `ava --serial`, commits updated `data/requests/` back to repo.
10 changes: 4 additions & 6 deletions ava.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ testOrder = Object.fromEntries(
);

export default {
cache: false,
cache: true,
serial: true,
concurrency: 0,
extensions: {
ts: "module"
},
extensions: ["js", "ts"],
files: ["tests/**/*"],
nodeArguments: ["--import=tsimp"],
// sortTestFiles: (file1, file2) => testData[file1].order - testData[file2].order,
nodeArguments: ["--import=tsx", "--trace-deprecation"],
sortTestFiles: (a, b) => {
a = path.relative(import.meta.dirname, a);
const aOrder = testOrder[a] || Infinity;
Expand Down
48 changes: 27 additions & 21 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,43 @@
"type": "module",
"main": "index.js",
"scripts": {
"test": "ava --serial"
"test": "ava",
"fast-fail": "ava --fail-fast",
"type-check": "tsc --noEmit",
"test:clean": "pwsh ./scripts/clean.ps1",
"get-spec": "pwsh ./scripts/get-spec.ps1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.6.4",
"devDependencies": {
"@apidevtools/json-schema-ref-parser": "^15.3.5",
"@ariesclark/eslint-config": "^3.1.1",
"@exodus/schemasafe": "^1.3.0",
"ava": "^6.1.3",
"@types/ms": "^2.1.0",
"@types/node": "^25.9.1",
"@types/object-path": "^0.11.4",
"@types/totp-generator": "^0.0.8",
"@types/tough-cookie": "^4.0.5",
"ava": "^8.0.1",
"change-case": "^5.4.4",
"dotenv": "^16.4.5",
"fetch-cookie": "^3.0.1",
"dotenv": "^17.4.2",
"eslint": "^10.4.1",
"eslint-plugin-ava": "^17.0.0",
"fetch-cookie": "^3.2.0",
"jsonpointer": "^5.0.1",
"ms": "^2.1.3",
"object-path": "^0.11.8",
"totp-generator": "^1.0.0",
"tough-cookie": "^4.1.4",
"vrchat": "^1.18.5",
"yaml": "^2.4.5"
},
"devDependencies": {
"@ariesclark/eslint-config": "^2.0.1",
"@types/ms": "^0.7.34",
"@types/object-path": "^0.11.4",
"@types/totp-generator": "^0.0.8",
"@types/tough-cookie": "^4.0.5",
"eslint": "^9.7.0",
"eslint-plugin-ava": "^15.0.1",
"openapi-types": "^12.1.3",
"totp-generator": "^2.0.1",
"tough-cookie": "^6.0.1",
"ts-node": "^10.9.2",
"tsimp": "^2.0.11",
"typescript": "^5.5.3"
"tsx": "^4.22.4",
"typescript": "^6.0.3",
"vrchat": "^2.21.7",
"yaml": "^2.9.0"
},
"engines": {
"node": "24"
}
}
Loading