Skip to main content
Glama
db-configure-indexes.test.ts8.69 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType } from '@medplum/core'; import express from 'express'; import { escapeIdentifier } from 'pg'; import request from 'supertest'; import { initApp, shutdownApp } from '../../app'; import { loadTestConfig } from '../../config/loader'; import { DatabaseMode, getDatabasePool } from '../../database'; import { initTestAuth, waitForAsyncJob } from '../../test.setup'; describe('dbgetginindexes', () => { const app = express(); let accessToken: string; const tableName = 'Gin_Index_Configure_Test_Table'; const escapedTableName = escapeIdentifier(tableName); beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); accessToken = await initTestAuth({ project: { superAdmin: true } }); // Create a test table const client = getDatabasePool(DatabaseMode.WRITER); await client.query(`DROP TABLE IF EXISTS ${escapedTableName}`); await client.query(`CREATE TABLE ${escapedTableName} (aaa UUID[], bbb TEXT[])`); await client.query( `CREATE INDEX CONCURRENTLY "${tableName}_aaa_idx" ON ${escapedTableName} USING gin (aaa) WITH (fastupdate = ye, gin_pending_list_limit = 1024)` ); await client.query(`CREATE INDEX CONCURRENTLY "${tableName}_bbb_idx" ON ${escapedTableName} USING gin (bbb)`); }); afterAll(async () => { await shutdownApp(); }); test('Must prefer async', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Parameters', parameter: [ { name: 'fastUpdate', valueBoolean: true, }, ], }); expect(res.status).toBe(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'invalid', details: { text: 'Operation requires "Prefer: respond-async"' }, }, ], }); }); test('tableName is required', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('Prefer', 'respond-async') .send({ resourceType: 'Parameters', parameter: [ { name: 'fastUpdate', valueBoolean: true, }, ], }); expect(res.status).toBe(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'invalid', details: { text: 'Expected 1..* value(s) for input parameter tableName, but 0 provided' }, }, ], }); }); test('fastUpdate or ginPendingListLimit is required', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('Prefer', 'respond-async') .send({ resourceType: 'Parameters', parameter: [ { name: 'tableName', valueString: tableName, }, ], }); expect(res.status).toBe(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'invalid', details: { text: 'At least one of fastUpdateAction or ginPendingListLimitAction must be specified' }, }, ], }); }); test('Success with tableName and fastupdate=on and ginPendingListLimit', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('Prefer', 'respond-async') .send({ resourceType: 'Parameters', parameter: [ { name: 'tableName', valueString: tableName, }, { name: 'fastUpdateAction', valueString: 'set', }, { name: 'fastUpdateValue', valueBoolean: true, }, { name: 'ginPendingListLimitAction', valueString: 'set', }, { name: 'ginPendingListLimitValue', valueInteger: 128, }, ], }); expect(res.status).toBe(202); const asyncJob = await waitForAsyncJob(res.headers['content-location'], app, accessToken); expect(asyncJob.output).toStrictEqual({ resourceType: 'Parameters', parameter: [ { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_aaa_idx" SET (fastupdate = true, gin_pending_list_limit = 128)', }, ], }, { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_bbb_idx" SET (fastupdate = true, gin_pending_list_limit = 128)', }, ], }, ], }); }); test('Success with tableName and fastupdate=off', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('Prefer', 'respond-async') .send({ resourceType: 'Parameters', parameter: [ { name: 'tableName', valueString: tableName, }, { name: 'fastUpdateAction', valueString: 'set', }, { name: 'fastUpdateValue', valueBoolean: false, }, { name: 'ginPendingListLimitAction', valueString: 'reset', }, ], }); expect(res.status).toBe(202); const asyncJob = await waitForAsyncJob(res.headers['content-location'], app, accessToken); expect(asyncJob.output).toStrictEqual({ resourceType: 'Parameters', parameter: [ { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_aaa_idx" RESET (gin_pending_list_limit)', }, ], }, { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_aaa_idx" SET (fastupdate = false)', }, ], }, { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_bbb_idx" RESET (gin_pending_list_limit)', }, ], }, { name: 'action', part: [ { name: 'sql', valueString: 'ALTER INDEX public."Gin_Index_Configure_Test_Table_bbb_idx" SET (fastupdate = false)', }, ], }, { name: 'action', part: [ { name: 'sql', valueString: 'VACUUM "Gin_Index_Configure_Test_Table"', }, { name: 'durationMs', valueInteger: expect.any(Number), }, ], }, ], }); }); test('Invalid tableName', async () => { const res = await request(app) .post('/fhir/R4/$db-configure-indexes') .set('Authorization', 'Bearer ' + accessToken) .set('Content-Type', ContentType.FHIR_JSON) .set('Prefer', 'respond-async') .send({ resourceType: 'Parameters', parameter: [ { name: 'tableName', valueString: 'Robert"; DROP TABLE Students;', }, { name: 'fastUpdate', valueBoolean: true, }, ], }); expect(res.status).toBe(400); expect(res.body).toMatchObject({ resourceType: 'OperationOutcome', issue: [ { severity: 'error', code: 'invalid', details: { text: 'Invalid tableName' }, }, ], }); }); });

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