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
6 changes: 3 additions & 3 deletions .github/workflows/js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x']
node-version: ['24.x']

steps:
- uses: actions/checkout@v4
Expand All @@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x']
node-version: ['24.x']

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -68,7 +68,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['20.x']
node-version: ['24.x']

steps:
- uses: actions/checkout@v4
Expand Down
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ any FHIR-based data exchange, and the team welcomes contributions.

Developers
interested in contributing to the Inferno Core gem must have [Ruby
3.1+](https://www.ruby-lang.org/en/), [Node.js and
3.3.6](https://www.ruby-lang.org/en/), [Node.js and
NPM](https://www.npmjs.com/get-npm), and [Docker
Desktop](https://www.docker.com/products/docker-desktop/) installed.
[Podman](https://podman.io/) may be used an alternative to Docker Desktop.
Expand Down Expand Up @@ -145,14 +145,25 @@ GET http://localhost:4567/inferno/api/test_sessions/TEST_SESSION_ID/results
```

## Development in a Ruby console
To get to an interactive console, run `bundle exec bin/inferno console`
To get to an interactive console, run `bundle exec inferno console`

## Updating the FHIR Resource Validator
Inferno relies on a java service to validate FHIR resources. [The validator
directory](https://github.com/inferno-framework/inferno-core/tree/main/validator)
contains the Dockerfile used to build this validator and instructions for
updating it.

## Updating NPM Packages

When updating the node packages recorded in the `package-lock.json` file, the file
needs to be generated as if from a linux system to match checks performed during
CI. A reliable way to do this is to use Docker to generate the file within a
linux environment regardless of your local development system.

```
docker run --rm -v $(pwd):/app -w /app node:24 npm install --package-lock-only
```

## Documentation
Inferno Core documentation has primarily moved to the
[Inferno Framework documentation site](https://github.com/inferno-framework/inferno-framework.github.io/).
Expand Down
40 changes: 40 additions & 0 deletions client/src/api/__tests__/RequestsApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, expect, it, vi, afterEach } from 'vitest';
import { getRequestDetails } from '~/api/RequestsApi';

afterEach(() => vi.unstubAllGlobals());

const mockRequest = {
id: 'req-1',
direction: 'outgoing',
verb: 'GET',
url: 'https://example.com',
index: 0,
status: 200,
timestamp: '2024-01-01T00:00:00Z',
result_id: 'r1',
};

describe('getRequestDetails', () => {
it('fetches and returns request details', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue(mockRequest),
}),
);

const result = await getRequestDetails('req-1');
expect(result).toEqual(mockRequest);
});

it('includes the request id in the endpoint URL', async () => {
const fetchMock = vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue(mockRequest),
});
vi.stubGlobal('fetch', fetchMock);

await getRequestDetails('req-abc-123');

expect(String(fetchMock.mock.calls[0][0])).toContain('req-abc-123');
});
});
80 changes: 80 additions & 0 deletions client/src/api/__tests__/RequirementsApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, it, vi, afterEach } from 'vitest';
import { getTestSuiteRequirements, getSingleRequirement } from '~/api/RequirementsApi';
import { Requirement } from '~/models/testSuiteModels';

afterEach(() => vi.unstubAllGlobals());

const mockRequirement: Requirement = {
id: 'req-1',
requirement: 'SHALL do something',
conformance: 'SHALL',
actor: 'Client',
conditionality: 'false',
subrequirements: [],
};

describe('getTestSuiteRequirements', () => {
it('fetches and returns a requirements list', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue([mockRequirement]),
}),
);

const result = await getTestSuiteRequirements('suite-1', 'session-1');
expect(result).toEqual([mockRequirement]);
});

it('returns an empty array when the response is null', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue(null),
}),
);

const result = await getTestSuiteRequirements('suite-1', 'session-1');
expect(result).toEqual([]);
});

it('returns an empty array when fetch throws', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Network error')));

const result = await getTestSuiteRequirements('suite-1', 'session-1');
expect(result).toEqual([]);
});
});

describe('getSingleRequirement', () => {
it('fetches and returns a single requirement', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue(mockRequirement),
}),
);

const result = await getSingleRequirement('req-1');
expect(result).toEqual(mockRequirement);
});

it('returns null when the response is null', async () => {
vi.stubGlobal(
'fetch',
vi.fn().mockResolvedValue({
json: vi.fn().mockResolvedValue(null),
}),
);

const result = await getSingleRequirement('req-1');
expect(result).toBeNull();
});

it('returns null when fetch throws', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('Network error')));

const result = await getSingleRequirement('req-1');
expect(result).toBeNull();
});
});
132 changes: 132 additions & 0 deletions client/src/api/__tests__/TestRunsApi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { describe, expect, it, vi, afterEach } from 'vitest';
import { postTestRun, deleteTestRun, getTestRunWithResults } from '~/api/TestRunsApi';
import { RunnableType, TestRun } from '~/models/testSuiteModels';

const mockTestRun: TestRun = { id: 'run-1', test_session_id: 'session-1', status: 'queued' };

function makeFetchMock(returnValue: unknown) {
return vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue(returnValue) });
}

afterEach(() => vi.unstubAllGlobals());

describe('postTestRun', () => {
it('sends test_suite_id for TestSuite runnable type', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await postTestRun('session-1', RunnableType.TestSuite, 'suite-1', []);

const init = fetchMock.mock.calls[0][1] as RequestInit;
const body = JSON.parse(init.body as string) as Record<string, unknown>;
expect(body['test_suite_id']).toBe('suite-1');
expect(body).not.toHaveProperty('test_group_id');
expect(body).not.toHaveProperty('test_id');
});

it('sends test_group_id for TestGroup runnable type', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await postTestRun('session-1', RunnableType.TestGroup, 'group-1', []);

const init = fetchMock.mock.calls[0][1] as RequestInit;
const body = JSON.parse(init.body as string) as Record<string, unknown>;
expect(body['test_group_id']).toBe('group-1');
expect(body).not.toHaveProperty('test_suite_id');
expect(body).not.toHaveProperty('test_id');
});

it('sends test_id for Test runnable type', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await postTestRun('session-1', RunnableType.Test, 'test-1', []);

const init = fetchMock.mock.calls[0][1] as RequestInit;
const body = JSON.parse(init.body as string) as Record<string, unknown>;
expect(body['test_id']).toBe('test-1');
expect(body).not.toHaveProperty('test_group_id');
expect(body).not.toHaveProperty('test_suite_id');
});

it('includes test_session_id and inputs in the body', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);
const inputs = [{ name: 'token', value: 'abc' }];

await postTestRun('session-1', RunnableType.TestSuite, 'suite-1', inputs);

const init = fetchMock.mock.calls[0][1] as RequestInit;
const body = JSON.parse(init.body as string) as Record<string, unknown>;
expect(body['test_session_id']).toBe('session-1');
expect(body['inputs']).toEqual(inputs);
});

it('uses POST method with application/json content-type', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await postTestRun('session-1', RunnableType.TestSuite, 'suite-1', []);

const init = fetchMock.mock.calls[0][1] as RequestInit;
expect(init.method).toBe('POST');
expect((init.headers as Record<string, string>)['Content-Type']).toBe('application/json');
});

it('returns the parsed TestRun', async () => {
vi.stubGlobal('fetch', makeFetchMock(mockTestRun));

const result = await postTestRun('session-1', RunnableType.TestSuite, 'suite-1', []);
expect(result).toEqual(mockTestRun);
});
});

describe('deleteTestRun', () => {
it('sends a DELETE request to an endpoint containing the run id', async () => {
const fetchMock = vi.fn().mockResolvedValue({ status: 204 });
vi.stubGlobal('fetch', fetchMock);

await deleteTestRun('run-1');

const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit];
expect(String(url)).toContain('run-1');
expect(init.method).toBe('DELETE');
});
});

describe('getTestRunWithResults', () => {
it('returns the fetched test run', async () => {
vi.stubGlobal('fetch', makeFetchMock(mockTestRun));

const result = await getTestRunWithResults('run-1', null);
expect(result).toEqual(mockTestRun);
});

it('appends an after param when time is provided', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await getTestRunWithResults('run-1', '2024-01-01T00:00:00Z');

expect(String(fetchMock.mock.calls[0][0])).toContain('after=2024-01-01T00:00:00Z');
});

it('does not append an after param when time is null', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await getTestRunWithResults('run-1', null);

expect(String(fetchMock.mock.calls[0][0])).not.toContain('after=');
});

it('does not append an after param when time is undefined', async () => {
const fetchMock = makeFetchMock(mockTestRun);
vi.stubGlobal('fetch', fetchMock);

await getTestRunWithResults('run-1', undefined);

expect(String(fetchMock.mock.calls[0][0])).not.toContain('after=');
});
});
Loading
Loading