diff --git a/packages/core/src/queries/connector.test.ts b/packages/core/src/queries/connector.test.ts index 191b7aa51..dcbe3d66f 100644 --- a/packages/core/src/queries/connector.test.ts +++ b/packages/core/src/queries/connector.test.ts @@ -4,12 +4,14 @@ import { createMockPool, createMockQueryResult, sql } from 'slonik'; import { mockConnector } from '@/__mocks__'; import envSet from '@/env-set'; +import { DeletionError } from '@/errors/SlonikError'; import type { QueryType } from '@/utils/test-utils'; import { expectSqlAssert } from '@/utils/test-utils'; import { findAllConnectors, countConnectorByConnectorId, + deleteConnectorById, insertConnector, updateConnector, } from './connector'; @@ -64,6 +66,43 @@ describe('connector queries', () => { await expect(countConnectorByConnectorId(rowData.connectorId)).resolves.toEqual(rowData); }); + it('deleteConnectorById', async () => { + const rowData = { id: 'foo' }; + const id = 'foo'; + const expectSql = sql` + delete from ${table} + where ${fields.id}=$1 + `; + + mockQuery.mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([id]); + + return createMockQueryResult([rowData]); + }); + + await deleteConnectorById(id); + }); + + it('deleteConnectorById should throw with zero response', async () => { + const id = 'foo'; + const expectSql = sql` + delete from ${table} + where ${fields.id}=$1 + `; + + mockQuery.mockImplementationOnce(async (sql, values) => { + expectSqlAssert(sql, expectSql.sql); + expect(values).toEqual([id]); + + return createMockQueryResult([]); + }); + + await expect(deleteConnectorById(id)).rejects.toMatchError( + new DeletionError(Connectors.table, id) + ); + }); + it('insertConnector', async () => { const connector = { ...mockConnector, diff --git a/packages/core/src/queries/connector.ts b/packages/core/src/queries/connector.ts index 22391dab3..2fe0a6069 100644 --- a/packages/core/src/queries/connector.ts +++ b/packages/core/src/queries/connector.ts @@ -6,6 +6,7 @@ import { sql } from 'slonik'; import { buildInsertInto } from '@/database/insert-into'; import { buildUpdateWhere } from '@/database/update-where'; import envSet from '@/env-set'; +import { DeletionError } from '@/errors/SlonikError'; const { table, fields } = convertToIdentifiers(Connectors); @@ -25,6 +26,17 @@ export const countConnectorByConnectorId = async (connectorId: string) => where ${fields.connectorId}=${connectorId} `); +export const deleteConnectorById = async (id: string) => { + const { rowCount } = await envSet.pool.query(sql` + delete from ${table} + where ${fields.id}=${id} + `); + + if (rowCount < 1) { + throw new DeletionError(Connectors.table, id); + } +}; + export const insertConnector = buildInsertInto(Connectors, { returning: true, }); diff --git a/packages/core/src/routes/connector.test.ts b/packages/core/src/routes/connector.test.ts index c45f0771d..272e35959 100644 --- a/packages/core/src/routes/connector.test.ts +++ b/packages/core/src/routes/connector.test.ts @@ -12,7 +12,7 @@ import { import { defaultConnectorMethods } from '@/connectors/consts'; import type { VirtualConnector, LogtoConnector } from '@/connectors/types'; import RequestError from '@/errors/RequestError'; -import { countConnectorByConnectorId } from '@/queries/connector'; +import { countConnectorByConnectorId, deleteConnectorById } from '@/queries/connector'; import assertThat from '@/utils/assert-that'; import { createRequester } from '@/utils/test-utils'; @@ -27,6 +27,7 @@ const getLogtoConnectorsPlaceHolder = jest.fn() as jest.MockedFunction< jest.mock('@/queries/connector', () => ({ countConnectorByConnectorId: jest.fn(), + deleteConnectorById: jest.fn(), insertConnector: jest.fn(async (body: unknown) => body), })); @@ -274,4 +275,15 @@ describe('connector route', () => { expect(response).toHaveProperty('statusCode', 400); }); }); + + describe('DELETE /connectors/:id', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('delete connector instance', async () => { + await connectorRequest.delete('/connectors/id').send({}); + expect(deleteConnectorById).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/core/src/routes/connector.ts b/packages/core/src/routes/connector.ts index 682b7a7e7..a65f12270 100644 --- a/packages/core/src/routes/connector.ts +++ b/packages/core/src/routes/connector.ts @@ -10,7 +10,12 @@ import type { LogtoConnector } from '@/connectors/types'; import RequestError from '@/errors/RequestError'; import { removeUnavailableSocialConnectorTargets } from '@/lib/sign-in-experience'; import koaGuard from '@/middleware/koa-guard'; -import { countConnectorByConnectorId, insertConnector, updateConnector } from '@/queries/connector'; +import { + countConnectorByConnectorId, + deleteConnectorById, + insertConnector, + updateConnector, +} from '@/queries/connector'; import assertThat from '@/utils/assert-that'; import type { AuthedRouter } from './types'; @@ -255,4 +260,20 @@ export default function connectorRoutes(router: T) { return next(); } ); + + router.delete( + '/connectors/:id', + koaGuard({ params: object({ id: string().min(1) }) }), + async (ctx, next) => { + const { + params: { id }, + } = ctx.guard; + + await deleteConnectorById(id); + + ctx.status = 204; + + return next(); + } + ); }