Skip to content

Functionality to support testing of Time zone offset - #119

Draft
bryantaustin13 wants to merge 14 commits into
mainfrom
TimeZoneOffset
Draft

Functionality to support testing of Time zone offset #119
bryantaustin13 wants to merge 14 commits into
mainfrom
TimeZoneOffset

Conversation

@bryantaustin13

Copy link
Copy Markdown
Contributor

This depends on the cql-tests PR cqframework/cql-tests#88 (comment) which contains the CQLTimeZoneOffsetTest.xml.
Adds functionality to test timezone offsets. See the README for more information.


Moved from #77, which was raised from a fork before I had committer access on this repository. Same work, same branch name, now hosted here so CI and reviews run against cqframework directly.

@bryantaustin13

Copy link
Copy Markdown
Contributor Author

Carried over from #77

This PR replaces #77, which was raised from a fork. The review discussion there did not
move with it, so it is reproduced below verbatim — these points are still open.

@cmoesel (2026-03-24):

I'm not sure I understand the premise of the offset timezone policy. In the description of the timezone offset policy in this PR's README, it says:

These settings are required because CQL allows DateTime values without a timezone offset, and different engines interpret those values differently.

and...

A DateTime like this:

@2012-04-01T00:00

has no timezone offset.

When a CQL engine evaluates this, it must decide:
👉 Should this value be treated as having a timezone, or not?

I don't think there should be any ambiguity here. The CQL specification is pretty clear about this (emphasis mine):

Only DateTime values may specify a timezone offset, either as UTC (Z), or as a timezone offset. If no timezone offset is specified, the timezone offset of the evaluation request timestamp is used.

So engines always treate DateTimes as having a timezone offset; it's just a question of which timezone offset (and that's indicated by the evaluation request timestamp).

Am I misunderstanding what the README is trying to say?


@bryantaustin13 (2026-03-27):

Please look at this again and let me know if this works and any changes you would like.


@brynrhodes (2026-04-02):

Add to the mapping of CQL values to/from FHIR support for the time-hasOffset extension described here: https://jira.hl7.org/browse/FHIR-56046


Open items from the above: @cmoesel's question about the premise of the offset timezone
policy, and @brynrhodes's request to support the time-hasOffset extension
(https://jira.hl7.org/browse/FHIR-56046) in the CQL-to-FHIR value mapping. Neither is
addressed on this branch yet.

This branch carried three test files that duplicate others already present,
adding 415 lines and no coverage. Each was checked for unique tests first;
all three had none.

  test/run-DataTransferItemList.test.ts  352 lines, a copy of run-tests.test.ts,
                                         which is also present. The name is the
                                         browser drag-and-drop API, so this looks
                                         like an accidental IDE rename.
  test/value-Map.test.ts                 byte-identical to value-map.test.ts,
                                         differing only in case. Two paths that
                                         differ only by case cannot coexist in a
                                         checkout on macOS or Windows.
  test/results-utils.test.js             a .js duplicate of results-utils.test.ts.

Also drops `import { response } from 'express'` from cql-engine.ts. It was
unused — the four other mentions of `response` are a local AxiosResponse — and
it pulled express into a module with no need for it.

Test files 11 -> 9 and tests 134 -> 122, all of the reduction duplicates. The
two remaining failures in run-tests.test.ts predate this change; they were
previously reported four times because the duplicate file doubled them.
tsc --noEmit clean.
Ports the timezone-offset policy feature onto the deduped runner and drops the
parts main has since covered.

Dropped, both entirely superseded:
  test-types.ts, results-shared.ts   this branch's groupCapability work. Nothing
                                     emitted the field, so it collected data that
                                     never reached the results file; #122 reports
                                     group capabilities through the existing
                                     capabilities field instead. Took main's
                                     versions of both files.
  test-runner.ts                     #117 moved the per-test logic into
                                     shared/run-test-core.ts, so the 260 lines
                                     this branch added there are ported rather
                                     than restored. Took main's version.

Ported into run-test-core.ts, so the CLI and server paths share it:
  - policy resolution (metadata -> env/config -> probe -> default), run once per
    execution context rather than per test
  - a skip arm for tests declaring a timezone-offset-policy capability that does
    not match the server's
  - {{SERVER_OFFSET_ISO}} substitution, applied to result.expression before the
    request is built so the expression sent and the expression reported match

Two changes from the original while porting:

Resolution is gated on the loaded suite actually containing a policy-dependent
test. It costs a metadata request and possibly a probe expression, and almost no
suite consults it; doing it unconditionally also broke tests that stub CQLEngine
or sequence fetch mocks.

The required policy is read from result.capability rather than test.capability.
In CQLTimeZoneOffsetTest.xml the policy is declared on the <group>, and one of
the three groups does not restate it on its tests — reading only the test level
silently never gated those. This depends on #122 to see group capabilities;
until that merges, group-level declarations still are not visible.

The probe now uses fetch only, since the dedupe removed the axios path, and
CQLEngine gains a serverMetadata getter so the resolution can read the
CapabilityStatement without reaching into a private field.
The feature had no tests, which matters because of how it merges: it is added
to the same region of run-test-core.ts that the library-style test support
touches, and resolving that conflict in favour of the other side silently drops
the skip arm and the substitution. Verified that mis-resolution leaves tsc
clean and every other test passing, so nothing would have caught it.

Eight tests: policy mismatch skips before any request is made, a matching
policy runs, a test declaring no policy runs whatever the server follows, and a
policy inherited from the group gates the same as one on the test itself. Then
the placeholder: substituted, substituted at every occurrence, left alone when
no offset is configured, and — the point of doing it before the request is
built — the expression sent to the server is the one recorded on the result.

Removing either the gate or the substitution fails five of the eight.
Pre-resolves the one conflict between the two, so that when #118 lands in main
this branch merges clean rather than presenting a resolution whose obvious
answer silently deletes a feature.

Both PRs add to the same region of runTest, after the version gate. The two
sides are not alternatives — keep all of it:

  - the timezone policy skip arm and the {{SERVER_OFFSET_ISO}} substitution,
    from this branch
  - the publishedLibrary declaration, from #118

Dropped only this branch's `const data = generateParametersResource(...)` line:
#118 moved that call inside the try, after publishing, so it can pass the
published library's canonical url. The substitution still happens before it,
which is what keeps the expression sent and the expression reported identical.

tsc clean, 214 tests passing, with both features verified present: policy gate,
offset substitution, library publish, canonical url, and removal in finally.
@bryantaustin13
bryantaustin13 marked this pull request as draft August 19, 2026 18:33
@@ -1,4 +1,4 @@
import { TestRunner } from '../services/test-runner.js';
import { TestRunner } from '../services/test-runner';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason for removing the the js extension? What impact does that have on converting the TS file to JS down the road?

Unless there is a good reason to remove the extension I recommend adding it back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants