Skip to main content
Glama
projectclone.test.ts14.5 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType, getReferenceString, isUUID, LOINC, Operator, streamToBuffer } from '@medplum/core'; import type { Binary, Bot, BundleEntry, ClientApplication, Login, Observation, Patient, Project, ProjectMembership, Reference, User, } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import { pwnedPassword } from 'hibp'; import fetch from 'node-fetch'; import { Readable } from 'stream'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { getBinaryStorage } from '../../storage/loader'; import { createTestProject, initTestAuth, setupPwnedPasswordMock, setupRecaptchaMock, withTestContext, } from '../../test.setup'; import { getSystemRepo } from '../repo'; import { createProject } from './projectinit'; jest.mock('node-fetch'); jest.mock('hibp'); describe('Project clone', () => { const app = express(); const systemRepo = getSystemRepo(); beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); (fetch as unknown as jest.Mock).mockClear(); (pwnedPassword as unknown as jest.Mock).mockClear(); setupPwnedPasswordMock(pwnedPassword as unknown as jest.Mock, 0); setupRecaptchaMock(fetch as unknown as jest.Mock, true); }); afterAll(async () => { await shutdownApp(); }); test('Forbidden', async () => { const accessToken = await initTestAuth(); const res = await request(app) .post(`/fhir/R4/Project/${randomUUID()}/$clone`) .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({}); expect(res.status).toBe(403); }); test('Success', async () => { const { project } = await createTestProject(); expect(project).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(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({}); expect(res.status).toBe(201); const newProjectId = res.body.id; expect(newProjectId).toBeDefined(); expect(isUUID(newProjectId)).toBe(true); expect(newProjectId).not.toStrictEqual(project.id); const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); const patientBundle = await systemRepo.search({ resourceType: 'Patient', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(patientBundle).toBeDefined(); expect(patientBundle.entry).toHaveLength(1); const obsBundle = await systemRepo.search({ resourceType: 'Observation', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(obsBundle).toBeDefined(); expect(obsBundle.entry).toHaveLength(1); expect((obsBundle.entry?.[0]?.resource as Observation).subject?.reference).toStrictEqual( getReferenceString(patientBundle.entry?.[0]?.resource as Patient) ); }); test('Success with project name in body', async () => { const { project } = await createTestProject({ withClient: true }); const newProjectName = 'A New Name for cloned project'; expect(project).toBeDefined(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ name: newProjectName }); expect(res.status).toBe(201); const newProjectId = res.body.id; const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); expect(newProject.name).toBe(newProjectName); const ProjectMembershipBundle = await systemRepo.search({ resourceType: 'ProjectMembership', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ProjectMembershipBundle).toBeDefined(); expect(ProjectMembershipBundle.entry?.length).toBeGreaterThanOrEqual(1); for (const entry of ProjectMembershipBundle.entry as BundleEntry[]) { const resource = entry.resource as ProjectMembership; expect(resource.project?.display).toBe(newProjectName); } const ClientApplicationBundle = await systemRepo.search({ resourceType: 'ClientApplication', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ClientApplicationBundle).toBeDefined(); expect(ClientApplicationBundle.entry).toHaveLength(1); for (const entry of ClientApplicationBundle.entry as BundleEntry[]) { const resource = entry.resource as ClientApplication; expect(resource.name).not.toContain(newProjectName); } }); test('Success with project name in body and has project name + Default Client in ClientApplication.name', async () => { const res1 = await request(app) .post('/auth/newuser') .type('json') .send({ firstName: 'Alexander', lastName: 'Hamilton', email: `alex${randomUUID()}@example.com`, password: 'password!@#', recaptchaToken: 'xyz', codeChallenge: 'xyz', codeChallengeMethod: 'plain', }); const login = await systemRepo.readResource<Login>('Login', res1.body.login); const user = await systemRepo.readReference<User>(login.user as Reference<User>); expect(res1.status).toBe(200); const { project } = await withTestContext(() => createProject('Test Project Name', user)); const newProjectName = 'A New Name for a cloned project'; expect(project).toBeDefined(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ name: newProjectName }); expect(res.status).toBe(201); const ClientApplicationBundle = await systemRepo.search({ resourceType: 'ClientApplication', filters: [{ code: '_project', operator: Operator.EQUALS, value: res.body.id }], }); expect(ClientApplicationBundle).toBeDefined(); expect(ClientApplicationBundle.entry).toHaveLength(1); for (const entry of ClientApplicationBundle.entry as BundleEntry[]) { const resource = entry.resource as ClientApplication; expect(resource.name).toContain(newProjectName); expect(resource.description).toContain(newProjectName); } }); test('Success with resource type in body', async () => { const { project } = await createTestProject({ withClient: true }); const resourceTypes = ['ProjectMembership']; expect(project).toBeDefined(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ resourceTypes }); expect(res.status).toBe(201); const newProjectId = res.body.id; const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); expect(newProject.name).toBeDefined(); const ProjectMembershipBundle = await systemRepo.search({ resourceType: 'ProjectMembership', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ProjectMembershipBundle).toBeDefined(); expect(ProjectMembershipBundle.entry?.length).toBeGreaterThanOrEqual(1); const ClientApplicationBundle = await systemRepo.search({ resourceType: 'ClientApplication', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ClientApplicationBundle).toBeDefined(); expect(ClientApplicationBundle.entry).toHaveLength(0); }); test.skip('Success with includeIds in body', async () => { const { project, membership } = await createTestProject({ withClient: true }); const includeIds = [membership.id]; expect(project).toBeDefined(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ includeIds }); expect(res.status).toBe(201); const newProjectId = res.body.id; const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); expect(newProject.name).toBeDefined(); const ProjectMembershipBundle = await systemRepo.search({ resourceType: 'ProjectMembership', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ProjectMembershipBundle).toBeDefined(); expect(ProjectMembershipBundle.entry?.length).toBeGreaterThanOrEqual(1); const ClientApplicationBundle = await systemRepo.search({ resourceType: 'ClientApplication', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ClientApplicationBundle).toBeDefined(); expect(ClientApplicationBundle.entry).toHaveLength(0); }); test('Success with excludeIds in body', async () => { const { project, membership } = await createTestProject({ withClient: true }); const excludeIds = [membership.id]; expect(project).toBeDefined(); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({ excludeIds }); expect(res.status).toBe(201); const newProjectId = res.body.id; const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); expect(newProject.name).toBeDefined(); const ProjectMembershipBundle = await systemRepo.search({ resourceType: 'ProjectMembership', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ProjectMembershipBundle).toBeDefined(); expect(ProjectMembershipBundle.entry?.length).toBe(0); const ClientApplicationBundle = await systemRepo.search({ resourceType: 'ClientApplication', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(ClientApplicationBundle).toBeDefined(); expect(ClientApplicationBundle.entry).toHaveLength(1); }); test('Success with Bot attachments', async () => { const { project, repo } = await createTestProject({ withRepo: true }); expect(project).toBeDefined(); await withTestContext(async () => { const sourceCodeBinary = await repo.createResource<Binary>({ resourceType: 'Binary', contentType: ContentType.JAVASCRIPT, }); await getBinaryStorage().writeBinary( sourceCodeBinary, 'test.js', ContentType.JAVASCRIPT, Readable.from('console.log("Hello world");') ); const bot = await repo.createResource<Bot>({ resourceType: 'Bot', name: 'Test Bot', sourceCode: { url: getReferenceString(sourceCodeBinary), }, }); const superAdminAccessToken = await initTestAuth({ superAdmin: true }); expect(superAdminAccessToken).toBeDefined(); const res = await request(app) .post(`/fhir/R4/Project/${project.id}/$clone`) .set('Authorization', 'Bearer ' + superAdminAccessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('X-Medplum', 'extended') .send({}); expect(res.status).toBe(201); const newProjectId = res.body.id; expect(newProjectId).toBeDefined(); expect(isUUID(newProjectId)).toBe(true); expect(newProjectId).not.toStrictEqual(project.id); const newProject = await systemRepo.readResource<Project>('Project', newProjectId); expect(newProject).toBeDefined(); const newBot = await systemRepo.searchOne<Bot>({ resourceType: 'Bot', filters: [{ code: '_project', operator: Operator.EQUALS, value: newProjectId }], }); expect(newBot).toBeDefined(); expect(newBot?.sourceCode?.url).toMatch(/Binary\/[a-z0-9-]+$/); expect(newBot?.sourceCode?.url).not.toStrictEqual(bot.sourceCode?.url); // Get the binary content const newBinary = await systemRepo.readReference<Binary>({ reference: newBot?.sourceCode?.url as string }); const newBinaryContent = await getBinaryStorage().readBinary(newBinary); const newBinaryStr = (await streamToBuffer(newBinaryContent)).toString('utf8'); expect(newBinaryStr).toStrictEqual('console.log("Hello world");'); }); }); });

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