Skip to main content
Glama
expunge.test.ts8.33 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType, LOINC } from '@medplum/core'; import type { Observation, Patient } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { DatabaseMode, getDatabasePool } from '../../database'; import { getRedis } from '../../redis'; import { createTestProject, initTestAuth, waitForAsyncJob, withTestContext } from '../../test.setup'; import { getSystemRepo } from '../repo'; import { SelectQuery } from '../sql'; import { Expunger } from './expunge'; describe('Expunge', () => { const app = express(); const systemRepo = getSystemRepo(); let superAdminAccessToken: string; beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); superAdminAccessToken = await initTestAuth({ superAdmin: true }); }); afterAll(async () => { await shutdownApp(); }); test('Forbidden', async () => { const accessToken = await initTestAuth(); const res = await request(app) .post(`/fhir/R4/Project/${randomUUID()}/$expunge`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({}); expect(res.status).toBe(403); }); test('Expunge single resource', async () => { const patient = await withTestContext(() => systemRepo.createResource<Patient>({ resourceType: 'Patient', name: [{ given: ['Alice'], family: 'Smith' }], }) ); expect(patient).toBeDefined(); // Expect the patient to be in the "Patient" and "Patient_History" tables expect(await existsInDatabase('Patient', patient.id)).toBe(true); expect(await existsInDatabase('Patient_History', patient.id)).toBe(true); expect(await existsInLookupTable('HumanName', patient.id)).toBe(true); // Expunge the resource const res = await request(app) .post(`/fhir/R4/Patient/${patient.id}/$expunge`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({}); expect(res.status).toBe(200); // Expect the patient to be removed from both tables expect(await existsInDatabase('Patient', patient.id)).toBe(false); expect(await existsInDatabase('Patient_History', patient.id)).toBe(false); // Also expect lookup table to be cleaned up expect(await existsInLookupTable('HumanName', patient.id)).toBe(false); }); test('Expunge project compartment', async () => { const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); const patient = await systemRepo.createResource<Patient>({ resourceType: 'Patient', meta: { project: project.id }, name: [{ given: ['Alice'], family: 'Smith' }], }); expect(patient).toBeDefined(); const obs = await systemRepo.createResource<Observation>({ resourceType: 'Observation', meta: { project: project.id }, status: 'final', code: { coding: [{ system: LOINC, code: '12345-6' }] }, subject: { reference: 'Patient/' + patient.id }, }); expect(obs).toBeDefined(); // Expunge the project const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$expunge?everything=true`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({}); expect(res.status).toBe(202); await waitForAsyncJob(res.headers['content-location'], app, superAdminAccessToken); expect(await existsInDatabase('Project', project.id)).toBe(false); expect(await existsInDatabase('ClientApplication', client.id)).toBe(false); expect(await existsInDatabase('ProjectMembership', membership.id)).toBe(false); }); test('Expunger.expunge() expunges all resource types', async () => { //setup const { project, client, membership } = await createTestProject({ withClient: true }); expect(project).toBeDefined(); expect(client).toBeDefined(); expect(membership).toBeDefined(); const patient = await systemRepo.createResource<Patient>({ resourceType: 'Patient', meta: { project: project.id }, name: [{ given: ['Alice'], family: 'Smith' }], }); expect(patient).toBeDefined(); const patient2 = await systemRepo.createResource<Patient>({ resourceType: 'Patient', meta: { project: project.id }, name: [{ given: ['Bob'], family: 'Smith' }], }); const patient3 = await systemRepo.createResource<Patient>({ resourceType: 'Patient', meta: { project: project.id }, name: [{ given: ['Bob'], family: 'Smith' }], }); expect(patient3).toBeDefined(); const obs = await systemRepo.createResource<Observation>({ resourceType: 'Observation', meta: { project: project.id }, status: 'final', code: { coding: [{ system: LOINC, code: '12345-6' }] }, subject: { reference: 'Patient/' + patient.id }, }); expect(obs).toBeDefined(); expect(await existsInCache('Project', project.id)).toBe(true); expect(await existsInCache('ClientApplication', client.id)).toBe(true); expect(await existsInCache('ProjectMembership', membership.id)).toBe(true); expect(await existsInCache('Patient', patient.id)).toBe(true); expect(await existsInCache('Patient', patient2.id)).toBe(true); expect(await existsInCache('Patient', patient3.id)).toBe(true); expect(await existsInCache('Observation', obs.id)).toBe(true); //execute await new Expunger(systemRepo, project.id, 2).expunge(); //result expect(await existsInDatabase('Project', project.id)).toBe(false); expect(await existsInDatabase('Project_History', project.id)).toBe(false); expect(await existsInDatabase('ClientApplication', client.id)).toBe(false); expect(await existsInDatabase('ClientApplication_History', client.id)).toBe(false); expect(await existsInDatabase('ProjectMembership', membership.id)).toBe(false); expect(await existsInDatabase('ProjectMembership_History', membership.id)).toBe(false); expect(await existsInDatabase('Patient', patient.id)).toBe(false); expect(await existsInDatabase('Patient_History', patient.id)).toBe(false); expect(await existsInDatabase('Patient', patient2.id)).toBe(false); expect(await existsInDatabase('Patient_History', patient2.id)).toBe(false); expect(await existsInDatabase('Patient', patient3.id)).toBe(false); expect(await existsInDatabase('Patient_History', patient3.id)).toBe(false); expect(await existsInDatabase('Observation', obs.id)).toBe(false); expect(await existsInDatabase('Observation_History', obs.id)).toBe(false); expect(await existsInCache('Project', project.id)).toBe(false); expect(await existsInCache('ClientApplication', client.id)).toBe(false); expect(await existsInCache('ProjectMembership', membership.id)).toBe(false); expect(await existsInCache('Patient', patient.id)).toBe(false); expect(await existsInCache('Patient', patient2.id)).toBe(false); expect(await existsInCache('Patient', patient3.id)).toBe(false); expect(await existsInCache('Observation', obs.id)).toBe(false); }); }); async function existsInCache(resourceType: string, id: string | undefined): Promise<boolean> { const redis = await getRedis().get(`${resourceType}/${id}`); return !!redis; } async function existsInDatabase(tableName: string, id: string | undefined): Promise<boolean> { const rows = await new SelectQuery(tableName) .column('id') .where('id', '=', id) .execute(getDatabasePool(DatabaseMode.READER)); return rows.length > 0; } async function existsInLookupTable(tableName: string, id: string | undefined): Promise<boolean> { const rows = await new SelectQuery(tableName) .column('resourceId') .where('resourceId', '=', id) .execute(getDatabasePool(DatabaseMode.READER)); return rows.length > 0; }

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