mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
ba7d55f999
BODY_CONTRACT_ALLOW_LIST is empty. The last seventeen handlers type their
body with a createZodDto class, so the fail-closed boot gate has nothing left
to forgive; it still refuses a boot for a new handler that reads @Body()
without one.
Several of those contracts describe rather than constrain, and that is the
whole care in this change. The pipe runs before the handler, so a schema
stricter than the handler moves a user-visible answer:
- TagsController.create answers { error: 'Tag name is required' }, pinned
byte-for-byte in two tests, and the pipe formats as `field: message`. The
DTO leaves name optional and the handler keeps the check that owns the
message. Same shape for the booking-import confirm body, which additionally
gains per-item validation the handler never did.
- PluginsController.activate and install answer 503 when plugins are disabled,
and check that BEFORE looking at the body. A schema that rejected first
would turn that into a 400 for an operator whose server has plugins off.
- setEgressHosts reads a non-array `hosts` as "clear the list", which is the
documented reset. Typing it as string[] would have removed a working admin
action rather than tightened anything.
- PluginRoutesController.route answers { route: null } at 200 for everything
it dislikes, so the client falls back to straight lines. A 400 there would
turn a graceful degradation into a visible error.
files.bridge is gone. The multer options are built by
MulterModule.registerAsync from an AllowedFileTypesService leaf, so the
fileFilter reads the operator's extension list from the container instead of
from a second FilesService built outside it. Nest injects
MULTER_MODULE_OPTIONS with @Optional(), so a token that fails to resolve is
not a boot error: it silently falls back to defaults, with no filter and no
size cap. FILE-020 already proved that for trip files by uploading an .svg
over HTTP and expecting 400; journey uploads had no such case, and now do.
VIDEO_UPLOAD is untouched, including the filename callback that forces .jpg
regardless of what the client sends, which guards the public share proxy.
PluginFrame gets an onError. A frame whose document fails to load left a
blank element at full height, which reads as a hung app rather than as a
plugin that is gone.
typecheck:tests runs in CI now, with the test tree at zero errors. tz-lookup
is left as it is: the build puts it in a lazy helpers chunk, not the entry
chunk, and the remaining split would trade a synchronous render for an async
one to move 172 kB that is already off the critical path.
239 lines
6.8 KiB
YAML
239 lines
6.8 KiB
YAML
name: Tests
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, dev]
|
|
paths:
|
|
- 'server/**'
|
|
- 'client/**'
|
|
- 'shared/**'
|
|
- 'nest-mcp/**'
|
|
- 'plugin-sdk/**'
|
|
- '.github/workflows/test.yml'
|
|
|
|
jobs:
|
|
plugin-facts:
|
|
name: Plugin Permission Facts
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: npm
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspace server
|
|
|
|
# The plugin permission facts live once, in
|
|
# server/src/nest/plugins/protocol/envelope.ts. Every other copy - the SDK's
|
|
# generated/host-facts.ts and shared's plugin-permissions.ts - is generated from it
|
|
# and checked in, because publish-plugin-sdk builds from a standalone checkout with
|
|
# no server present. This fails the PR if a copy drifts, if a new permission has no
|
|
# admin.plugins.perm.* translation, or if the CLI has no hint for it.
|
|
- name: Check the generated plugin facts are in sync
|
|
run: cd server && npm run check:plugin-facts
|
|
|
|
i18n-parity:
|
|
name: i18n Key Parity
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
|
|
- name: Check i18n key parity
|
|
run: node shared/scripts/i18n-parity.mjs --strict
|
|
|
|
shared-contracts:
|
|
name: Shared Contracts (Zod)
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspace shared
|
|
|
|
- name: Typecheck
|
|
run: cd shared && npm run typecheck
|
|
|
|
- name: Run tests
|
|
run: cd shared && npm test
|
|
|
|
nest-mcp-package:
|
|
name: nest-mcp Package
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspace nest-mcp
|
|
|
|
- name: Ensure @swc/core's Linux binary for unplugin-swc
|
|
# Same lockfile quirk as server-tests: the Linux native binary is
|
|
# omitted, and nest-mcp's vitest config uses the SWC transform too.
|
|
run: |
|
|
SWC_VERSION=$(node -p "require('@swc/core/package.json').version")
|
|
npm install --no-save --legacy-peer-deps "@swc/core-linux-x64-gnu@$SWC_VERSION"
|
|
|
|
- name: Build
|
|
run: npm run build --workspace=nest-mcp
|
|
|
|
- name: Typecheck
|
|
run: cd nest-mcp && npm run typecheck
|
|
|
|
- name: Lint
|
|
run: cd nest-mcp && npm run lint:check
|
|
|
|
- name: Run tests
|
|
run: cd nest-mcp && npm test
|
|
|
|
server-tests:
|
|
name: Server Tests
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Ensure @swc/core's Linux binary for unplugin-swc
|
|
# The lockfile was generated on Windows and omits @swc/core's Linux
|
|
# optional native binary, so npm ci/install skips it on the runner.
|
|
# Install the matching version explicitly so the server's SWC transform
|
|
# (server/vitest.config.ts) can load.
|
|
run: |
|
|
SWC_VERSION=$(node -p "require('@swc/core/package.json').version")
|
|
npm install --no-save --legacy-peer-deps "@swc/core-linux-x64-gnu@$SWC_VERSION"
|
|
|
|
- name: Build shared
|
|
run: npm run build --workspace=shared
|
|
|
|
- name: Build nest-mcp
|
|
# Server typecheck/build resolve @trek/nest-mcp's types from its dist
|
|
# (tests alias the package source, but tsc does not).
|
|
run: npm run build --workspace=nest-mcp
|
|
|
|
- name: Build server (tsc -> dist)
|
|
run: cd server && npm run build
|
|
|
|
- name: Smoke production require chain
|
|
# Vitest aliases @trek/nest-mcp to its source, so only this exercises
|
|
# what production runs: nest-mcp's built dist resolving the MCP SDK's
|
|
# subpath exports through the tsconfig-paths/register runtime hook.
|
|
run: cd server && node --require tsconfig-paths/register -e "require('@trek/nest-mcp')"
|
|
|
|
- name: Typecheck
|
|
run: cd server && npm run typecheck
|
|
|
|
- name: Typecheck tests
|
|
# tests/ is outside the build tsconfig, so a service that grows a
|
|
# constructor parameter does not break its hand-wired call sites at
|
|
# compile time: the argument arrives as undefined and throws at runtime,
|
|
# if anything reads it at all. This is the step that catches it.
|
|
run: cd server && npm run typecheck:tests
|
|
|
|
- name: Lint
|
|
run: cd server && npm run lint:check
|
|
|
|
- name: Run tests
|
|
run: cd server && npm run test:coverage
|
|
|
|
- name: Upload coverage
|
|
if: success()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: backend-coverage
|
|
path: server/coverage/
|
|
retention-days: 7
|
|
|
|
client-quality:
|
|
# Split out of client-tests: the suite takes ~11 minutes, and having the
|
|
# gates in front of it meant a single lint finding threw away the whole test
|
|
# signal for that run. Both jobs pay the install/build, which is cheap next
|
|
# to running the two in series.
|
|
name: Client Types & Lint
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspace shared && npm ci --workspace client
|
|
|
|
- name: Build shared
|
|
run: npm run build --workspace=shared
|
|
|
|
- name: Typecheck
|
|
run: cd client && npm run typecheck
|
|
|
|
- name: Lint
|
|
run: cd client && npm run lint:check
|
|
|
|
- name: Page pattern check
|
|
run: cd client && npm run lint:pages
|
|
|
|
client-tests:
|
|
name: Client Tests
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: 24
|
|
cache: npm
|
|
cache-dependency-path: package-lock.json
|
|
|
|
- name: Install dependencies
|
|
run: npm ci --workspace shared && npm ci --workspace client
|
|
|
|
- name: Build shared
|
|
run: npm run build --workspace=shared
|
|
|
|
- name: Run tests
|
|
run: cd client && npm run test:coverage
|
|
|
|
- name: Upload coverage
|
|
if: success()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: frontend-coverage
|
|
path: client/coverage/
|
|
retention-days: 7
|