Skip to content
Merged
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
22 changes: 19 additions & 3 deletions src/test-results/cql-test-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ import { TestResult, InternalTestResult } from '../models/test-types.js';
import { TestResultsSummary, CQLTestResultsData } from '../models/results-types.js';
import { ResultsValidator } from '../conf/results-validator.js';

/**
* Formats an actual value for the results file. Lists are rendered in CQL list
* syntax (`{ a, b, c }`) so they read like the expected value, which is kept in
* its original CQL/CVL notation.
*/
function formatActualValue(value: any): string {
if (Array.isArray(value)) {
return value.length === 0 ? '{}' : `{ ${value.map(formatActualValue).join(', ')} }`;
}
if (value !== null && typeof value === 'object') {
return JSON.stringify(value);
}
return String(value);
}

/**
* Represents the results of running CQL tests.
*/
Expand Down Expand Up @@ -106,7 +121,7 @@ export class CQLTestResults {
...(result.responseStatus !== undefined && {
responseStatus: result.responseStatus,
}),
...(result.actual !== undefined && { actual: String(result.actual) }),
...(result.actual !== undefined && { actual: formatActualValue(result.actual) }),
...(result.expected && { expected: result.expected }),
...(result.error && {
error: {
Expand Down Expand Up @@ -188,8 +203,9 @@ export class CQLTestResults {
} else if (typeof act === 'number' && typeof exp === 'string') {
r.actual = String(act);
} else if (act !== undefined && act !== null && typeof act !== 'string') {
// Convert any non-string value to string for schema compliance
r.actual = String(act);
// Convert any non-string value to string for schema compliance;
// lists are rendered in CQL list syntax to mirror the expected value
r.actual = formatActualValue(act);
}
}
}
Expand Down
64 changes: 64 additions & 0 deletions test/cql-test-results-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,67 @@ describe('CQLTestResults.validateSchema', () => {
expect(ResultsValidator).toHaveBeenCalled();
});
});

describe('CQLTestResults.toJSON actual serialization', () => {
it('renders list results in CQL list syntax and stringifies scalar results', () => {
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
results.add({
testsName: 'CqlListOperatorsTest',
groupName: 'Sort',
testName: 'SortDatesAsc',
expression: 'sort asc',
testStatus: 'fail',
invalid: 'false',
actual: ['@2012-01-01T', '@2012-01-01T12'],
expected: '{ @2012-01-01T, @2012-01-01T12 }',
} as any);
results.add({
testsName: 'CqlArithmeticFunctionsTest',
groupName: 'Power',
testName: 'Power2To4',
expression: '2^4',
testStatus: 'pass',
invalid: 'false',
actual: 16,
expected: '16',
} as any);

const json = results.toJSON();
expect(json.results[0].actual).toBe('{ @2012-01-01T, @2012-01-01T12 }');
expect(json.results[1].actual).toBe('16');
});

it('equalizeValueTypes renders array actuals in CQL list syntax', () => {
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
results.add({
testsName: 'T',
groupName: 'G',
testName: 'N',
expression: 'e',
testStatus: 'pass',
invalid: 'false',
actual: [1, 2, 3],
expected: '{ 1, 2, 3 }',
} as any);

results.equalizeValueTypes();
expect(results.results[0].actual).toBe('{ 1, 2, 3 }');
});

it('renders nested lists and empty lists', () => {
const results = new CQLTestResults(new CQLEngine('http://localhost:8080/fhir/$cql'));
results.add({
testsName: 'T',
groupName: 'G',
testName: 'Nested',
expression: 'e',
testStatus: 'pass',
invalid: 'false',
actual: [[1, 2], []],
expected: '{ { 1, 2 }, {} }',
} as any);

const json = results.toJSON();
expect(json.results[0].actual).toBe('{ { 1, 2 }, {} }');
});
});