Skip to content

Commit 5bc96b7

Browse files
authored
CQL Test Server for CQL Tests Runner (#360)
Implement a test server that exposes a `$cql` target to allow the CQL Tests Runner to run tests against the `cql-execution`. The test server is a sub-project at `test-server` with its own `package.json` file and corresponding scripts. See test-server/README.md for further details.
1 parent d1b1b14 commit 5bc96b7

39 files changed

Lines changed: 36667 additions & 6 deletions

.github/workflows/ci-workflow.yml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- uses: actions/setup-node@v6
3030
with:
3131
node-version: 24.x
32-
- run: npm install
32+
- run: npm ci
3333
- run: npm run lint
3434
- run: npm run prettier
3535
- run: ./bin/check_for_nonassertive_tests.sh
@@ -49,7 +49,7 @@ jobs:
4949
with:
5050
timezone: America/New_York
5151
- run: date +"%Y-%m-%d %T"
52-
- run: npm install
52+
- run: npm ci
5353
- run: npx nyc --reporter=lcov npm test
5454
env:
5555
CI: true
@@ -58,3 +58,18 @@ jobs:
5858
uses: codecov/codecov-action@v5
5959
with:
6060
token: ${{ secrets.CODECOV_TOKEN }}
61+
test-server:
62+
name: Check test-server
63+
runs-on: ubuntu-latest
64+
steps:
65+
- uses: actions/checkout@v6
66+
- uses: actions/setup-node@v6
67+
with:
68+
node-version: 24.x
69+
- run: npm ci
70+
- run: npm ci
71+
working-directory: test-server
72+
- run: npm run build
73+
working-directory: test-server
74+
- run: npm run check
75+
working-directory: test-server

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ node_modules
55
.vscode
66
coverage
77
cql4browsers.js
8+
# the following uses its own prettier config
9+
test-server
810
# the following are generated by the test generator
911
test/elm/*/data.js
1012
test/elm/external/Included.js

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,10 @@ modification to the test data text file, you can setup a process to _watch_ for
350350
regenerate the `data.js` files every time it detects changes in the source text file. Simply
351351
execute `npm run watch:test-data`.
352352

353+
# Using the Test Server and CQL Tests Runner
354+
355+
See [test-server/README.md](test-server/README.md).
356+
353357
# Pull Requests
354358

355359
If TypeScript source code is modified, `cql4browsers.js` needs to be included in the pull request,

bin/validate_cql4browsers.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ if [ $? != 0 ]; then
1313
echo "cql4browsers.js not found. Run this script from the base repository directory."
1414
exit 1
1515
fi
16-
npm install
16+
npm ci
1717

1818
# comm -3 only returns lines that differ between the two files. If none are different, diff will be empty
1919
diff=`diff ./examples/browser/cql4browsers.js.original ./examples/browser/cql4browsers.js`

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default defineConfig([
1919
'**/.eslintrc.json',
2020
'**/lib',
2121
'**/lib-test',
22+
'test-server',
2223
'examples/browser/cql4browsers.js',
2324
'test/elm/library/data-with-namespace.js'
2425
]),

test-server/.gitignore

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
lib-cov
2+
*.seed
3+
*.log
4+
*.csv
5+
*.dat
6+
*.out
7+
*.pid
8+
*.gz
9+
*.swp
10+
11+
pids
12+
logs
13+
results
14+
tmp
15+
16+
# API keys and secrets
17+
.env
18+
19+
# Dependency directory
20+
node_modules
21+
22+
# Editors
23+
.vscode
24+
.idea
25+
*.iml
26+
27+
# OS metadata
28+
.DS_Store
29+
Thumbs.db
30+
31+
# Ignore built ts files
32+
dist/**/*

test-server/.prettierrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"trailingComma": "none",
3+
"arrowParens": "avoid",
4+
"endOfLine": "auto",
5+
"printWidth": 100,
6+
"tabWidth": 2,
7+
"singleQuote": true
8+
}

test-server/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# CQL Test Server
2+
3+
A barebones implementation of the FHIR [$cql](https://build.fhir.org/ig/HL7/cql-ig/en/OperationDefinition-cql-cql.html) operation that evaluates CQL expressions using the CQL execution engine and uses the [CQL FHIR Type Mapping](https://build.fhir.org/ig/HL7/cql-ig/en/conformance.html#fhir-type-mapping) to return results as a FHIR `Parameters` resource. This server is intended to be used as a target for the [cql-tests-runner](https://github.com/cqframework/cql-tests-runner) and is tuned specifically for that use case.
4+
5+
## Endpoint
6+
7+
- POST `/fhir/$cql`
8+
- Example request:
9+
```json
10+
{
11+
"resourceType": "Parameters",
12+
"parameter": [{ "name": "expression", "valueString": "1 + 2" }]
13+
}
14+
```
15+
- Example response:
16+
```json
17+
{
18+
"resourceType": "Parameters",
19+
"parameter": [{
20+
"extension": [{
21+
"url": "http://hl7.org/fhir/StructureDefinition/cqf-cqlType",
22+
"valueString": "System.Integer"
23+
}],
24+
"name": "return",
25+
"valueInteger": 3
26+
}]
27+
}
28+
```
29+
- Errors:
30+
- HTTP 400 if the `expression` parameter is missing or empty
31+
- HTTP 500 if there is an unexpected / unrecoverable server-side error
32+
33+
## Prerequisites
34+
35+
This project uses Node.js. It has been tested using Node 24 but should work with any recent LTS version.
36+
37+
## Developing Scripts
38+
39+
The `package.json` file defines scripts to support development of the test-server source code. These include:
40+
41+
- `npm run lint`: run eslint against the test-server source code
42+
- `npm run lint:fix`: fix eslint errors (as possible)
43+
- `npm run prettier`: run prettier against the test-server source code
44+
- `npm run prettier:fix`: fix prettier violations (as possible)
45+
- `npm test`: run the unit tests
46+
47+
Scripts for building and running the server are documented below.
48+
49+
## Start The Server
50+
51+
From this directory:
52+
53+
### Install dependencies:
54+
55+
```sh
56+
npm install
57+
```
58+
59+
### Development mode:
60+
61+
```sh
62+
npm run dev
63+
```
64+
65+
The development mode builds the `cql-execution` source (to ensure it uses the latest changes) before running the server. It watches for changes to the `test-server` source and hot-reloads as necessary. By default, it uses the javascript CQL-to-ELM translator and listens on `http://localhost:8000`.
66+
67+
_NOTE: The watch mode will NOT detect or hot-reload changes in the `cql-execution` source. You must stop and restart the `test-server` to pick up changes in the `cql-execution` source._
68+
69+
# Production mode:
70+
71+
```sh
72+
npm run build
73+
npm start
74+
```
75+
76+
The production mode builds the `cql-execution` source and `test-server` source then runs the server _without_ watching for changes. By default, it uses the javascript CQL-to-ELM translator and listens on `http://localhost:8000`.
77+
78+
### Configuration
79+
80+
- `PORT` (optional): Port to bind. Defaults to `8000`.
81+
- `USE_TRANSLATION_SERVICE` (optional, default: `false`): If `true`, the server will use a local [CQL Translation Service](https://github.com/cqframework/cql-translation-service) for CQL-to-ELM. In this case, the CQL Translation service must be running and available at `http://localhost:8080`. If `false`, the server will use the javascript CQL-to-ELM module to compile CQL.
82+
83+
You can export these variables before running the server or place them in a `.env` file:
84+
85+
```ini
86+
PORT=8000
87+
USE_TRANSLATION_SERVICE=false
88+
```
89+
90+
## Quick Test With curl
91+
92+
```sh
93+
curl -X POST http://localhost:8000/fhir/\$cql \
94+
-H "Content-Type: application/fhir+json" \
95+
-d '{
96+
"resourceType": "Parameters",
97+
"parameter": [
98+
{ "name": "expression", "valueString": "1 + 2" }
99+
]
100+
}'
101+
```
102+
103+
## Use With cql-tests-runner
104+
105+
This server is designed to be a `$cql` target for the [cql-tests-runner](https://github.com/cqframework/cql-tests-runner). To execute the test-runner against this server:
106+
107+
1. Download or clone the `cql-tests-runner` repository.
108+
109+
2. Start this test-server (see above), e.g. at `http://localhost:8000`.
110+
111+
3. In cql-tests-runner folder, create or edit a [test runner configuration](https://github.com/cqframework/cql-tests-runner?tab=readme-ov-file#configuration-settings) that points to the test-server endpoint. If you are using port 8000, you may choose to use the existing [conf/cql-execution-local.json](https://github.com/cqframework/cql-tests-runner/blob/main/conf/cql-execution-local.json) file.
112+
113+
4. Run the tests from the cql-tests-runner folder:
114+
115+
```sh
116+
npm install
117+
git submodule update --init --recursive
118+
npx tsx src/bin/cql-tests.ts run-tests conf/cql-execution-local.json ./results
119+
```
120+
121+
The runner will POST each test expression to `http://localhost:8000/fhir/$cql` and expect FHIR `Parameters` responses per the mapping. The test-server logs each incoming expression, the raw result, and the result mapped to `FHIR Parameters`. When the run is done, you can find the results in the cql-tests-runner folder's `results` sub-folder.
122+

test-server/eslint.config.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { defineConfig, globalIgnores } from 'eslint/config';
2+
import globals from 'globals';
3+
import tsParser from '@typescript-eslint/parser';
4+
import path from 'node:path';
5+
import { fileURLToPath } from 'node:url';
6+
import js from '@eslint/js';
7+
import { FlatCompat } from '@eslint/eslintrc';
8+
9+
const __filename = fileURLToPath(import.meta.url);
10+
const __dirname = path.dirname(__filename);
11+
const compat = new FlatCompat({
12+
baseDirectory: __dirname,
13+
recommendedConfig: js.configs.recommended,
14+
allConfig: js.configs.all
15+
});
16+
17+
export default defineConfig([
18+
globalIgnores(['dist', 'node_modules']),
19+
{
20+
extends: compat.extends('plugin:@typescript-eslint/recommended', 'prettier'),
21+
languageOptions: {
22+
globals: {
23+
...globals.node,
24+
...globals.mocha
25+
},
26+
parser: tsParser,
27+
ecmaVersion: 6,
28+
sourceType: 'module',
29+
parserOptions: {
30+
ecmaFeatures: { experimentalObjectRestSpread: true }
31+
}
32+
},
33+
rules: {
34+
'@typescript-eslint/no-require-imports': 'off',
35+
'@typescript-eslint/no-explicit-any': 'off',
36+
'no-unused-vars': 'off', // redundant to rule below
37+
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
38+
indent: ['error', 2, { SwitchCase: 1 }],
39+
'no-console': ['warn', { allow: ['warn', 'error'] }],
40+
'no-loss-of-precision': 'off',
41+
quotes: ['error', 'single', { allowTemplateLiterals: true, avoidEscape: true }],
42+
semi: ['error', 'always'],
43+
curly: 'error'
44+
}
45+
},
46+
{
47+
files: ['test/**/*.{js,ts}', 'examples/**/*.js', 'bin/**/*.js'],
48+
rules: {
49+
'@typescript-eslint/no-var-requires': 'off'
50+
}
51+
}
52+
]);

0 commit comments

Comments
 (0)