Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
127 changes: 50 additions & 77 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -494,85 +494,13 @@ jobs:
if: needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true'
runs-on: ubuntu-latest
concurrency:
group: realm-server-test-${{ matrix.testModule }}-${{ github.head_ref || github.run_id }}
group: realm-server-test-${{ matrix.shardIndex }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
testModule:
[
"auth-client-test.ts",
"billing-test.ts",
"card-dependencies-endpoint-test.ts",
"card-endpoints-test.ts",
"card-reference-resolver-test.ts",
"card-source-endpoints-test.ts",
"definition-lookup-test.ts",
"file-watcher-events-test.ts",
"indexing-event-sink-test.ts",
"indexing-test.ts",
"runtime-dependency-tracker-test.ts",
"transpile-test.ts",
"module-syntax-test.ts",
"node-realm-test.ts",
"permissions/permission-checker-test.ts",
"prerendering-test.ts",
"prerender-server-test.ts",
"prerender-manager-test.ts",
"prerender-proxy-test.ts",
"remote-prerenderer-test.ts",
"queue-test.ts",
"realm-endpoints/cancel-indexing-job-test.ts",
"realm-endpoints/dependencies-test.ts",
"realm-endpoints/directory-test.ts",
"realm-endpoints/info-test.ts",
"realm-endpoints/invalidate-urls-test.ts",
"realm-endpoints/lint-test.ts",
"realm-endpoints/mtimes-test.ts",
"realm-endpoints/permissions-test.ts",
"realm-endpoints/publishability-test.ts",
"realm-endpoints/reindex-test.ts",
"realm-endpoints/search-test.ts",
"realm-endpoints/user-test.ts",
"realm-endpoints-test.ts",
"sanitize-head-html-test.ts",
"search-prerendered-test.ts",
"types-endpoint-test.ts",
"server-config-test.ts",
"server-endpoints/authentication-test.ts",
"server-endpoints/bot-commands-test.ts",
"server-endpoints/bot-registration-test.ts",
"server-endpoints/delete-realm-test.ts",
"server-endpoints/download-realm-test.ts",
"server-endpoints/index-responses-test.ts",
"server-endpoints/maintenance-endpoints-test.ts",
"server-endpoints/queue-status-test.ts",
"server-endpoints/realm-lifecycle-test.ts",
"server-endpoints/search-test.ts",
"server-endpoints/info-test.ts",
"server-endpoints/search-prerendered-test.ts",
"server-endpoints/stripe-session-test.ts",
"server-endpoints/stripe-webhook-test.ts",
"server-endpoints/user-and-catalog-test.ts",
"server-endpoints/incoming-webhook-test.ts",
"server-endpoints/webhook-commands-test.ts",
"server-endpoints/webhook-receiver-test.ts",
"server-endpoints/federated-types-test.ts",
"virtual-network-test.ts",
"atomic-endpoints-test.ts",
"request-forward-test.ts",
"publish-unpublish-realm-test.ts",
"boxel-domain-availability-test.ts",
"claim-boxel-domain-test.ts",
"command-parsing-utils-test.ts",
"delete-boxel-claimed-domain-test.ts",
"get-boxel-claimed-domain-test.ts",
"realm-auth-test.ts",
"run-command-task-test.ts",
"queries-test.ts",
"session-room-queries-test.ts",
"full-reindex-test.ts",
]
shardIndex: [1, 2, 3, 4, 5, 6]
shardTotal: [6]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/init
Expand All @@ -586,6 +514,10 @@ jobs:
run: |
shopt -s dotglob
cp -a .test-web-assets-artifact/. ./
- name: Compute shard test modules
id: shard_modules
run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shardIndex }} 6)" >> "$GITHUB_OUTPUT"
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step hardcodes the total shard count as 6 even though the matrix already defines shardTotal. To avoid drift if the shard count changes later, pass ${{ matrix.shardTotal }} to shard-test-modules.js instead of a constant.

Suggested change
run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shardIndex }} 6)" >> "$GITHUB_OUTPUT"
run: echo "modules=$(node scripts/shard-test-modules.js ${{ matrix.shardIndex }} ${{ matrix.shardTotal }})" >> "$GITHUB_OUTPUT"

Copilot uses AI. Check for mistakes.
working-directory: packages/realm-server
- name: Start test services (icons + host dist + realm servers)
run: mise run test-services:realm-server | tee -a /tmp/server.log &
- name: create realm users
Expand All @@ -595,15 +527,23 @@ jobs:
run: pnpm test:wait-for-servers
working-directory: packages/realm-server
env:
TEST_MODULE: ${{matrix.testModule}}
TEST_MODULES: ${{ steps.shard_modules.outputs.modules }}
JUNIT_OUTPUT_FILE: ${{ github.workspace }}/junit/realm-server-${{ matrix.shardIndex }}.xml
- name: Upload junit report
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1
if: ${{ !cancelled() }}
with:
name: realm-server-test-report-${{ matrix.shardIndex }}
path: junit/realm-server-${{ matrix.shardIndex }}.xml
retention-days: 30
- name: Print realm server logs
if: ${{ !cancelled() }}
run: cat /tmp/server.log
- name: Prepare artifact name
id: artifact_name
if: ${{ !cancelled() }}
run: |
export SAFE_ARTIFACT_NAME=$(echo ${{ matrix.testModule }} | sed 's/[/]/_/g')
export SAFE_ARTIFACT_NAME=shard-${{ matrix.shardIndex }}
echo "artifact_name=$SAFE_ARTIFACT_NAME" >> "$GITHUB_OUTPUT"
- name: Extract worker and prerender logs
if: ${{ !cancelled() }}
Expand Down Expand Up @@ -672,6 +612,39 @@ jobs:
path: /tmp/host-dist.log
retention-days: 30

realm-server-merge-reports:
name: Merge Realm Server reports and publish
if: ${{ !cancelled() && (needs.change-check.outputs.realm-server == 'true' || github.ref == 'refs/heads/main' || needs.change-check.outputs.run_all == 'true') }}
concurrency:
group: realm-server-merge-reports-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
needs: [change-check, realm-server-test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/init
- name: Download JUnit reports
uses: actions/download-artifact@b14cf4c92620c250e1c074ab0a5800e37df86765 # 4.2.0
with:
path: all-realm-server-reports
pattern: realm-server-test-report-*
merge-multiple: true
- name: Merge reports
run: npx junit-report-merger realm-server.xml "./all-realm-server-reports/*.xml"
- name: Upload merged report
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # 4.6.1
if: ${{ !cancelled() }}
with:
name: realm-server-test-report-merged
path: realm-server.xml
retention-days: 30
- name: Publish test results
uses: EnricoMi/publish-unit-test-result-action@170bf24d20d201b842d7a52403b73ed297e6645b # 2.18.0
if: ${{ !cancelled() }}
with:
junit_files: realm-server.xml
check_name: Realm Server Test Results

software-factory-test:
name: Software Factory Tests
needs: [change-check, test-web-assets]
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@
`pnpm test`
- Run a single module:
`TEST_MODULE=card-endpoints-test.ts pnpm test-module`
- Run a list of modules:
`TEST_MODULES=card-endpoints-test.ts|another-module-test.ts pnpm test`
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TEST_MODULES example needs quoting (or escaping) because | is a shell pipe. As written, TEST_MODULES=card-endpoints-test.ts|another-module-test.ts pnpm test will try to pipe to another-module-test.ts as a command instead of setting the env var. Use TEST_MODULES="card-endpoints-test.ts|another-module-test.ts" pnpm test (or single quotes).

Suggested change
`TEST_MODULES=card-endpoints-test.ts|another-module-test.ts pnpm test`
`TEST_MODULES="card-endpoints-test.ts|another-module-test.ts" pnpm test`

Copilot uses AI. Check for mistakes.
- Focusing on single test or module:
Add `.only` to module/test declaration (`test.only('returns a 201 response', ...)`)
Then run `pnpm test`
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,11 @@ First make sure to generate the host app's `dist/` output in order to support ca
To run the `packages/realm-server/` workspace tests start:

1. `mise run dev` from the repo root to serve _both_ the base realm and the realm that serves the test cards for node.
2. Run `pnpm test` in the `packages/realm-server/` workspace to run the realm node tests. `TEST_MODULE=realm-endpoints-test.ts pnpm test-module` is an example of how to run a single test module.
2. Run the realm server tests:

- `pnpm test` in the `packages/realm-server/` workspace to run the realm node tests in full (~1hr).
- `TEST_MODULES="types-endpoint-test.ts|another-test-module.ts" pnpm test` in the `packages/realm-server/` workspace to run tests for a subset of modules.
- `TEST_MODULE="types-endpoint-test.ts" pnpm test-module` in the `packages/realm-server/` workspace to run tests for a specific module.

### Boxel UI

Expand Down
3 changes: 1 addition & 2 deletions packages/realm-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
"start:host-dist": "./scripts/start-host-dist.sh",
"start:pg": "./scripts/start-pg.sh",
"stop:pg": "./scripts/stop-pg.sh",
"test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test-module'",
"test:wait-for-servers": "WAIT_ON_TIMEOUT=900000 NODE_NO_WARNINGS=1 start-server-and-test 'pnpm run wait' 'http-get://localhost:4201/base/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson' 'pnpm run wait' 'http-get://localhost:4202/node-test/_readiness-check?acceptHeader=application%2Fvnd.api%2Bjson|http://localhost:8008|http://localhost:5001' 'test'",
"setup:base-in-deployment": "mkdir -p /persistent/base && rsync --dry-run --itemize-changes --checksum --recursive --delete ../base/. /persistent/base/ && rsync --checksum --recursive --delete ../base/. /persistent/base/",
"setup:experiments-in-deployment": "mkdir -p /persistent/experiments && rsync --dry-run --itemize-changes --checksum --recursive ../experiments-realm/. /persistent/experiments/ && rsync --checksum --recursive ../experiments-realm/. /persistent/experiments/",
"setup:catalog-in-deployment": "mkdir -p /persistent/catalog && rsync --dry-run --itemize-changes --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/ && rsync --checksum --recursive --delete ../catalog-realm/. /persistent/catalog/",
Expand Down Expand Up @@ -131,7 +131,6 @@
"lint:js": "eslint . --report-unused-disable-directives --cache",
"lint:js:fix": "eslint . --report-unused-disable-directives --fix",
"lint:glint": "glint",
"lint:test-shards": "ts-node --transpileOnly scripts/lint-test-shards.ts",
"full-reset": "./scripts/full-reset.sh",
"full-reindex": "./scripts/full-reindex.sh",
"clear-modules-cache": "NODE_NO_WARNINGS=1 PGDATABASE=${PGDATABASE:-boxel} PGPORT=${PGPORT:-5435} ts-node --transpileOnly scripts/clear-modules-cache.ts",
Expand Down
111 changes: 111 additions & 0 deletions packages/realm-server/scripts/junit-reporter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/* eslint-env node */
'use strict';

// QUnit JUnit XML reporter for CI.
//
// Usage: qunit --reporter junit-reporter.js ...
// Or: qunit --require ./scripts/junit-reporter.js --reporter console ...
//
// Set JUNIT_OUTPUT_FILE to control the output path (default: junit/realm-server.xml).
// When used as a --require module, this attaches alongside the default console
// reporter so you get both terminal output and a JUnit file.

const fs = require('node:fs'); // eslint-disable-line @typescript-eslint/no-var-requires
const path = require('node:path'); // eslint-disable-line @typescript-eslint/no-var-requires
const QUnit = require('qunit'); // eslint-disable-line @typescript-eslint/no-var-requires

const outputFile =
process.env.JUNIT_OUTPUT_FILE ||
path.join(process.cwd(), '..', '..', 'junit', 'realm-server.xml');

const suites = new Map(); // moduleName -> { tests, failures, errors, time, testCases }

function escapeXml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}

QUnit.on('testEnd', (data) => {
const moduleName = data.module || 'default';
const testName = data.name || 'unknown';
const runtime = (data.runtime || 0) / 1000; // ms → seconds
const status = data.status; // passed, failed, skipped, todo

if (!suites.has(moduleName)) {
suites.set(moduleName, {
tests: 0,
failures: 0,
errors: 0,
skipped: 0,
time: 0,
testCases: [],
});
}

const suite = suites.get(moduleName);
suite.tests++;
suite.time += runtime;

let caseXml = ` <testcase classname="${escapeXml(moduleName)}" name="${escapeXml(testName)}" time="${runtime.toFixed(3)}"`;

if (status === 'failed') {
suite.failures++;
const messages = (data.errors || [])
.map((e) => {
let msg = e.message || '';
if (e.actual !== undefined && e.expected !== undefined) {
msg += `\nExpected: ${JSON.stringify(e.expected)}\nActual: ${JSON.stringify(e.actual)}`;
}
if (e.stack) {
msg += `\n${e.stack}`;
}
return msg;
})
.join('\n---\n');
caseXml += `>\n <failure message="${escapeXml((data.errors?.[0]?.message || 'test failed').slice(0, 200))}">${escapeXml(messages)}</failure>\n </testcase>`;
} else if (status === 'skipped' || status === 'todo') {
suite.skipped++;
caseXml += `>\n <skipped />\n </testcase>`;
} else {
caseXml += ` />`;
}

suite.testCases.push(caseXml);
});

QUnit.on('runEnd', () => {
const suitesXml = [];
let totalTests = 0;
let totalFailures = 0;
let totalErrors = 0;
let totalSkipped = 0;
let totalTime = 0;

for (const [name, suite] of suites) {
totalTests += suite.tests;
totalFailures += suite.failures;
totalErrors += suite.errors;
totalSkipped += suite.skipped;
totalTime += suite.time;

suitesXml.push(
` <testsuite name="${escapeXml(name)}" tests="${suite.tests}" failures="${suite.failures}" errors="${suite.errors}" skipped="${suite.skipped}" time="${suite.time.toFixed(3)}">\n${suite.testCases.join('\n')}\n </testsuite>`,
);
}

const xml = [
`<?xml version="1.0" encoding="UTF-8"?>`,
`<testsuites name="Realm Server Tests" tests="${totalTests}" failures="${totalFailures}" errors="${totalErrors}" skipped="${totalSkipped}" time="${totalTime.toFixed(3)}">`,
...suitesXml,
`</testsuites>`,
].join('\n');

const dir = path.dirname(outputFile);
fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(outputFile, xml, 'utf8');
});
82 changes: 0 additions & 82 deletions packages/realm-server/scripts/lint-test-shards.ts

This file was deleted.

Loading
Loading