Skip to main content
Glama
asyncjobcancel.test.ts6.36 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { allOk, badRequest, ContentType } from '@medplum/core'; import type { FhirRequest } from '@medplum/fhir-router'; import type { AsyncJob, OperationOutcome } from '@medplum/fhirtypes'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { initTestAuth, withTestContext } from '../../test.setup'; import { getSystemRepo } from '../repo'; import { asyncJobCancelHandler } from './asyncjobcancel'; const app = express(); describe('AsyncJob/$cancel', () => { let accessToken: string; beforeAll(async () => { const config = await loadTestConfig(); await withTestContext(() => initApp(app, config)); }); beforeEach(async () => { accessToken = await initTestAuth({ superAdmin: true }); expect(accessToken).toBeDefined(); }); afterAll(async () => { await shutdownApp(); }); test('Sets AsyncJob.status to `cancelled`', async () => { const res = await request(app) .post('/fhir/R4/AsyncJob') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'AsyncJob', status: 'accepted', requestTime: new Date().toISOString(), request: 'random-request', } satisfies AsyncJob); expect(res.status).toStrictEqual(201); expect(res.body).toBeDefined(); const asyncJob = res.body as AsyncJob; const res2 = await request(app) .post(`/fhir/R4/AsyncJob/${asyncJob.id}/$cancel`) .set('Authorization', 'Bearer ' + accessToken); expect(res2.status).toStrictEqual(200); expect(res2.body).toMatchObject<OperationOutcome>(allOk); const res3 = await request(app) .get(`/fhir/R4/AsyncJob/${asyncJob.id}`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON); expect(res3.status).toStrictEqual(200); expect(res3.body).toMatchObject<AsyncJob>({ id: asyncJob.id, resourceType: 'AsyncJob', status: 'cancelled', requestTime: asyncJob.requestTime, request: 'random-request', }); }); test('No-op when AsyncJob is already `cancelled`', async () => { const res = await request(app) .post('/fhir/R4/AsyncJob') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'AsyncJob', status: 'cancelled', requestTime: new Date().toISOString(), request: 'random-request', } satisfies AsyncJob); expect(res.status).toStrictEqual(201); expect(res.body).toBeDefined(); const asyncJob = res.body as AsyncJob; const res2 = await request(app) .post(`/fhir/R4/AsyncJob/${asyncJob.id}/$cancel`) .set('Authorization', 'Bearer ' + accessToken); expect(res2.status).toStrictEqual(200); expect(res2.body).toMatchObject<OperationOutcome>(allOk); const res3 = await request(app) .get(`/fhir/R4/AsyncJob/${asyncJob.id}`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON); expect(res3.status).toStrictEqual(200); expect(res3.body).toMatchObject<AsyncJob>({ id: asyncJob.id, resourceType: 'AsyncJob', status: 'cancelled', requestTime: asyncJob.requestTime, request: 'random-request', }); }); test.each(['completed', 'error'] as const)('Fails if AsyncJob.status is `%s`', async (status) => { const res = await request(app) .post('/fhir/R4/AsyncJob') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'AsyncJob', status, requestTime: new Date().toISOString(), request: 'random-request', } satisfies AsyncJob); expect(res.status).toStrictEqual(201); expect(res.body).toBeDefined(); const asyncJob = res.body as AsyncJob; const res2 = await request(app) .post(`/fhir/R4/AsyncJob/${asyncJob.id}/$cancel`) .set('Authorization', 'Bearer ' + accessToken); expect(res2.status).toStrictEqual(400); const outcome = res2.body as OperationOutcome; expect(outcome).toMatchObject<OperationOutcome>( badRequest(`AsyncJob cannot be cancelled if status is not 'accepted', job had status '${status}'`) ); }); test('Fails if not executed on an instance (no ID given)', async () => { const req = { method: 'POST', url: 'AsyncJob/$cancel', pathname: '', params: {}, query: {}, body: '', headers: {}, } satisfies FhirRequest; await expect(asyncJobCancelHandler(req)).rejects.toThrow( new Error('This operation can only be executed on an instance') ); }); test('Cancelled job does not get added to super admin project', () => withTestContext(async () => { // We create the resource with system repo so that it is like how system AsyncJobs get created const asyncJob = await getSystemRepo().createResource<AsyncJob>({ resourceType: 'AsyncJob', status: 'accepted', requestTime: new Date().toISOString(), request: 'random-request', }); const res2 = await request(app) .post(`/fhir/R4/AsyncJob/${asyncJob.id}/$cancel`) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended'); expect(res2.status).toStrictEqual(200); expect(res2.body).toMatchObject(allOk); const res3 = await request(app) .get(`/fhir/R4/AsyncJob/${asyncJob.id}`) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended'); expect(res3.status).toStrictEqual(200); expect(res3.body).toStrictEqual({ id: asyncJob.id, resourceType: 'AsyncJob', requestTime: asyncJob.requestTime, request: 'random-request', status: 'cancelled', meta: { lastUpdated: expect.any(String), versionId: expect.any(String), author: { reference: 'system', }, // We make sure meta does not contain project }, }); })); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server