From 495dd1c4ea1eb862ed619e0c2fcec08e03a3a88b Mon Sep 17 00:00:00 2001 From: oniani1 Date: Thu, 30 Apr 2026 00:23:56 +0400 Subject: [PATCH] fix: allow PUT tenant to clear database_pool_url and database_pool_mode --- src/http/routes/admin/tenants.ts | 4 +- src/test/admin-tenants.test.ts | 74 ++++++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/http/routes/admin/tenants.ts b/src/http/routes/admin/tenants.ts index b37b87090..e60e3b9f9 100644 --- a/src/http/routes/admin/tenants.ts +++ b/src/http/routes/admin/tenants.ts @@ -605,8 +605,8 @@ export default async function routes(fastify: FastifyInstance) { tenantInfo.feature_s3_protocol = features?.s3Protocol?.enabled } - if (databasePoolUrl) { - tenantInfo.database_pool_url = encrypt(databasePoolUrl) + if (databasePoolUrl !== undefined) { + tenantInfo.database_pool_url = databasePoolUrl === null ? null : encrypt(databasePoolUrl) } if (maxConnections) { diff --git a/src/test/admin-tenants.test.ts b/src/test/admin-tenants.test.ts index 8122b4c68..84a8333bf 100644 --- a/src/test/admin-tenants.test.ts +++ b/src/test/admin-tenants.test.ts @@ -2,6 +2,29 @@ import { closeMultitenantPg } from '@internal/database' import * as migrations from '@internal/database/migrations' import { adminApp } from './common' +const adminHeaders = { + apikey: process.env.ADMIN_API_KEYS!, + 'content-type': 'application/json', +} + +const baseTenantBody = { + anonKey: 'anon', + databaseUrl: 'postgresql://postgres:postgres@127.0.0.1:5433/postgres', + jwtSecret: 'secret', + serviceKey: 'service', + jwks: null, + fileSizeLimit: 1024, +} + +beforeAll(async () => { + await migrations.runMultitenantMigrations() +}) + +afterAll(async () => { + await adminApp.close() + await multitenantKnex.destroy() +}) + describe('admin tenant delete route', () => { beforeAll(async () => { await migrations.runMultitenantMigrations() @@ -16,10 +39,7 @@ describe('admin tenant delete route', () => { const response = await adminApp.inject({ method: 'DELETE', url: '/tenants/abc', - headers: { - apikey: process.env.ADMIN_API_KEYS!, - 'content-type': 'application/json', - }, + headers: adminHeaders, payload: '', }) @@ -27,3 +47,49 @@ describe('admin tenant delete route', () => { expect(response.body).toBe('') }) }) + +describe('admin tenant put route nullable pool fields', () => { + const tenantId = 'put-nullable-pool' + + beforeAll(async () => { + await multitenantKnex('tenants').where('id', tenantId).delete() + }) + + afterAll(async () => { + await multitenantKnex('tenants').where('id', tenantId).delete() + }) + + it('clears database_pool_url and database_pool_mode when put with null', async () => { + const initial = await adminApp.inject({ + method: 'PUT', + url: `/tenants/${tenantId}`, + headers: adminHeaders, + payload: JSON.stringify({ + ...baseTenantBody, + databasePoolUrl: 'postgresql://postgres:postgres@127.0.0.1:6454/postgres', + databasePoolMode: 'transaction', + }), + }) + expect(initial.statusCode).toBe(204) + + const seeded = await multitenantKnex('tenants').first().where('id', tenantId) + expect(seeded?.database_pool_url).toBeTruthy() + expect(seeded?.database_pool_mode).toBe('transaction') + + const cleared = await adminApp.inject({ + method: 'PUT', + url: `/tenants/${tenantId}`, + headers: adminHeaders, + payload: JSON.stringify({ + ...baseTenantBody, + databasePoolUrl: null, + databasePoolMode: null, + }), + }) + expect(cleared.statusCode).toBe(204) + + const after = await multitenantKnex('tenants').first().where('id', tenantId) + expect(after?.database_pool_url).toBeNull() + expect(after?.database_pool_mode).toBeNull() + }) +})