Skip to main content
Glama
me.test.ts10.9 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { createReference, resolveId } from '@medplum/core'; import type { UserConfiguration } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; import { inviteUser } from '../admin/invite'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config/loader'; import { getSystemRepo } from '../fhir/repo'; import { createTestProject, withTestContext } from '../test.setup'; import { getUserConfigurationMenu } from './me'; import { registerNew } from './register'; const app = express(); describe('Me', () => { beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); }); afterAll(async () => { await shutdownApp(); }); test('Unauthenticated', async () => { const res = await request(app).get('/auth/me'); expect(res.status).toBe(401); }); test('User configuration', async () => { const { project, membership, accessToken } = await withTestContext(() => registerNew({ firstName: 'Alexander', lastName: 'Hamilton', projectName: 'Hamilton Project', email: `alex${randomUUID()}@example.com`, password: 'password!@#', remoteAddress: '5.5.5.5', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/107.0.0.0', }) ); // Get the user profile with default user configuration const res2 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res2.status).toBe(200); expect(res2.body).toBeDefined(); expect(res2.body.profile).toBeDefined(); expect(res2.body.profile.resourceType).toBe('Practitioner'); expect(res2.body.config).toBeDefined(); expect(res2.body.config.resourceType).toBe('UserConfiguration'); // Create a new user configuration const config: UserConfiguration = { resourceType: 'UserConfiguration', menu: [ { title: 'My Menu', link: [{ name: 'My Link', target: '/my-target' }], }, ], }; const res3 = await request(app) .post('/fhir/R4/UserConfiguration') .set('Authorization', `Bearer ${accessToken}`) .type('json') .send(config); expect(res3.status).toBe(201); expect(res3.body.resourceType).toBe('UserConfiguration'); expect(res3.body.id).toBeDefined(); expect(res3.body).toMatchObject(config); // Read the project membership const res4 = await request(app) .get(`/admin/projects/${project.id}/members/${membership.id}`) .set('Authorization', 'Bearer ' + accessToken); expect(res4.status).toBe(200); expect(res4.body.resourceType).toBe('ProjectMembership'); // Update the project membership const res5 = await request(app) .post(`/admin/projects/${project.id}/members/${membership.id}`) .set('Authorization', 'Bearer ' + accessToken) .type('json') .send({ ...res4.body, userConfiguration: createReference(res3.body), }); expect(res5.status).toBe(200); // As super admin user, add an identifier to the user const systemRepo = getSystemRepo(); await systemRepo.patchResource('User', resolveId(res4.body.user) as string, [ { op: 'add', path: '/identifier', value: [{ system: 'http://example.com', value: '12345' }], }, ]); // Reload the user profile with the new user configuration const res6 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res6.status).toBe(200); expect(res6.body).toBeDefined(); expect(res6.body.config).toMatchObject(config); expect(res6.body.security).toBeDefined(); expect(res6.body.security.sessions).toBeDefined(); expect(res6.body.security.sessions[0].browser).toBeDefined(); expect(res6.body.security.sessions[0].os).toBeDefined(); expect(res6.body.user.identifier).toBeDefined(); expect(res6.body.user.identifier.length).toBe(1); expect(res6.body.user.identifier[0]).toMatchObject({ system: 'http://example.com', value: '12345' }); }); test('Set default menu', async () => { const { project, membership, accessToken } = await withTestContext(() => registerNew({ firstName: 'Alexander', lastName: 'Hamilton', projectName: 'Hamilton Project', email: `alex${randomUUID()}@example.com`, password: 'password!@#', remoteAddress: '5.5.5.5', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/107.0.0.0', }) ); // Get the user profile with default user configuration const res2 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res2.status).toBe(200); expect(res2.body).toBeDefined(); expect(res2.body.profile).toBeDefined(); expect(res2.body.profile.resourceType).toBe('Practitioner'); expect(res2.body.config).toBeDefined(); expect(res2.body.config.resourceType).toBe('UserConfiguration'); expect(res2.body.config.menu).toMatchObject(getUserConfigurationMenu(project, membership)); // Create a new user configuration const config: UserConfiguration = { resourceType: 'UserConfiguration', option: [{ id: 'fhirQuota', valueInteger: 1000 }], }; const res3 = await request(app) .post('/fhir/R4/UserConfiguration') .set('Authorization', `Bearer ${accessToken}`) .type('json') .send(config); expect(res3.status).toBe(201); expect(res3.body.resourceType).toBe('UserConfiguration'); expect(res3.body.id).toBeDefined(); expect(res3.body).toMatchObject(config); // Read the project membership const res4 = await request(app) .get(`/admin/projects/${project.id}/members/${membership.id}`) .set('Authorization', 'Bearer ' + accessToken); expect(res4.status).toBe(200); expect(res4.body.resourceType).toBe('ProjectMembership'); // Update the project membership const res5 = await request(app) .post(`/admin/projects/${project.id}/members/${membership.id}`) .set('Authorization', 'Bearer ' + accessToken) .type('json') .send({ ...res4.body, userConfiguration: createReference(res3.body), }); expect(res5.status).toBe(200); // Reload the user profile with the new user configuration const res6 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res6.status).toBe(200); expect(res6.body).toBeDefined(); expect(res6.body.config.menu).toBeDefined(); expect(res2.body.config.menu).toMatchObject(getUserConfigurationMenu(project, membership)); }); test('Get me as ClientApplication', async () => { const { client } = await withTestContext(() => registerNew({ firstName: 'Client', lastName: 'Test', projectName: 'Client Test', email: `client${randomUUID()}@example.com`, password: 'password!@#', }) ); const res = await request(app) .get('/auth/me') .set('Authorization', 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64')); expect(res.status).toBe(200); expect(res.body.error).toBeUndefined(); }); test('AccessPolicy.basedOn', async () => { const { accessToken, accessPolicy } = await withTestContext(() => createTestProject({ withAccessToken: true, accessPolicy: { resource: [{ resourceType: 'Patient' }], }, }) ); const res = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res.status).toBe(200); expect(res.body).toBeDefined(); expect(res.body.accessPolicy).toBeDefined(); expect(res.body.accessPolicy.basedOn).toBeDefined(); expect(res.body.accessPolicy.basedOn).toMatchObject([createReference(accessPolicy)]); }); test('Get user memberships', async () => { const email = `alice${randomUUID()}@example.com`; const password = randomUUID(); const { project, membership, accessToken } = await withTestContext(() => registerNew({ firstName: 'Alexander', lastName: 'Hamilton', projectName: 'Memberships in /auth/me Part 1', email, password, remoteAddress: '5.5.5.5', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/107.0.0.0', }) ); // Get the user profile the initial memberships const res1 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res1.status).toBe(200); expect(res1.body).toBeDefined(); expect(res1.body.security.memberships).toHaveLength(1); expect(res1.body.security.memberships[0]).toMatchObject({ resourceType: 'ProjectMembership', id: membership.id, }); // Create an additional ProjectMembership for the same user in the same project const inviteResponse = await inviteUser({ project, email, password, resourceType: 'Practitioner', firstName: 'Alexander', lastName: 'Hamilton', sendEmail: false, forceNewMembership: true, }); expect(inviteResponse).toBeDefined(); // Reload the user profile with the new user memberships const res2 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res2.status).toBe(200); expect(res2.body).toBeDefined(); expect(res2.body.security.memberships).toHaveLength(2); // Register a totally separate project await withTestContext(() => registerNew({ firstName: 'Alexander', lastName: 'Hamilton', projectName: 'Memberships in /auth/me Part 2', email, password, remoteAddress: '5.5.5.5', userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/107.0.0.0', }) ); // Reload, should only see the 2 memberships from the original project const res3 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res3.status).toBe(200); expect(res3.body).toBeDefined(); expect(res3.body.security.memberships).toHaveLength(2); // Mark the 2nd ProjectMembership as inactive await getSystemRepo().patchResource('ProjectMembership', inviteResponse.membership.id, [ { op: 'add', path: '/active', value: false, }, ]); // Reload, should only see the 1 active membership const res4 = await request(app).get('/auth/me').set('Authorization', `Bearer ${accessToken}`); expect(res4.status).toBe(200); expect(res4.body).toBeDefined(); expect(res4.body.security.memberships).toHaveLength(1); }); });

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