From ab923879eecde8ed5b1c30a81d05ad996cfbe63e Mon Sep 17 00:00:00 2001 From: "georgiy.rusanov" Date: Sat, 23 Aug 2025 18:09:47 +0200 Subject: [PATCH 1/3] chore: added tests --- test/admin-app.test.ts | 17 +++++ test/app.test.ts | 31 ++++++++++ test/config.test.ts | 127 ++++++++++++++++++++++++++++++++++++++ test/extensions.test.ts | 33 ++++++++++ test/functions.test.ts | 33 ++++++++++ test/index.test.ts | 12 ++++ test/lib/utils.ts | 4 +- test/publications.test.ts | 33 ++++++++++ test/roles.test.ts | 33 ++++++++++ test/schemas.test.ts | 33 ++++++++++ test/triggers.test.ts | 33 ++++++++++ test/types.test.ts | 33 ++++++++++ test/utils.test.ts | 105 +++++++++++++++++++++++++++++++ test/views.test.ts | 51 +++++++++++++++ 14 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 test/admin-app.test.ts create mode 100644 test/app.test.ts create mode 100644 test/config.test.ts create mode 100644 test/extensions.test.ts create mode 100644 test/functions.test.ts create mode 100644 test/publications.test.ts create mode 100644 test/roles.test.ts create mode 100644 test/schemas.test.ts create mode 100644 test/triggers.test.ts create mode 100644 test/types.test.ts create mode 100644 test/utils.test.ts create mode 100644 test/views.test.ts diff --git a/test/admin-app.test.ts b/test/admin-app.test.ts new file mode 100644 index 00000000..0bc93e2e --- /dev/null +++ b/test/admin-app.test.ts @@ -0,0 +1,17 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/admin-app.js' + +describe('admin-app', () => { + test('should register metrics endpoint', async () => { + const app = build() + + // Test that the app can be started (this will trigger plugin registration) + await app.ready() + + // Verify that metrics endpoint is available + const routes = app.printRoutes() + expect(routes).toContain('metrics') + + await app.close() + }) +}) diff --git a/test/app.test.ts b/test/app.test.ts new file mode 100644 index 00000000..c705dd9b --- /dev/null +++ b/test/app.test.ts @@ -0,0 +1,31 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' + +describe('server/app', () => { + test('should handle root endpoint', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/', + }) + expect(response.statusCode).toBe(200) + const data = JSON.parse(response.body) + expect(data).toHaveProperty('status') + expect(data).toHaveProperty('name') + expect(data).toHaveProperty('version') + expect(data).toHaveProperty('documentation') + await app.close() + }) + + test('should handle health endpoint', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/health', + }) + expect(response.statusCode).toBe(200) + const data = JSON.parse(response.body) + expect(data).toHaveProperty('date') + await app.close() + }) +}) diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 00000000..2736eb57 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,127 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/config', () => { + test('should list config with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/config?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + [ + { + "boot_val": "on", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": null, + "min_val": null, + "name": "autovacuum", + "pending_restart": false, + "reset_val": "on", + "setting": "on", + "short_desc": "Starts the autovacuum subprocess.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "bool", + }, + { + "boot_val": "0.1", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "100", + "min_val": "0", + "name": "autovacuum_analyze_scale_factor", + "pending_restart": false, + "reset_val": "0.1", + "setting": "0.1", + "short_desc": "Number of tuple inserts, updates, or deletes prior to analyze as a fraction of reltuples.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "real", + }, + { + "boot_val": "50", + "category": "Autovacuum", + "context": "sighup", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "2147483647", + "min_val": "0", + "name": "autovacuum_analyze_threshold", + "pending_restart": false, + "reset_val": "50", + "setting": "50", + "short_desc": "Minimum number of tuple inserts, updates, or deletes prior to analyze.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + { + "boot_val": "200000000", + "category": "Autovacuum", + "context": "postmaster", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "2000000000", + "min_val": "100000", + "name": "autovacuum_freeze_max_age", + "pending_restart": false, + "reset_val": "200000000", + "setting": "200000000", + "short_desc": "Age at which to autovacuum a table to prevent transaction ID wraparound.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + { + "boot_val": "3", + "category": "Autovacuum", + "context": "postmaster", + "enumvals": null, + "extra_desc": null, + "group": "Autovacuum", + "max_val": "262143", + "min_val": "1", + "name": "autovacuum_max_workers", + "pending_restart": false, + "reset_val": "3", + "setting": "3", + "short_desc": "Sets the maximum number of simultaneously running autovacuum worker processes.", + "source": "default", + "sourcefile": null, + "sourceline": null, + "subgroup": "", + "unit": null, + "vartype": "integer", + }, + ] + `) + await app.close() + }) +}) diff --git a/test/extensions.test.ts b/test/extensions.test.ts new file mode 100644 index 00000000..06149d0d --- /dev/null +++ b/test/extensions.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/extensions', () => { + test('should list extensions', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list extensions with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/functions.test.ts b/test/functions.test.ts new file mode 100644 index 00000000..3bdec649 --- /dev/null +++ b/test/functions.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/functions', () => { + test('should list functions', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list functions with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/index.test.ts b/test/index.test.ts index 6ca2b87e..d879d232 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -24,3 +24,15 @@ import './server/table-privileges' import './server/typegen' import './server/result-size-limit' import './server/query-timeout' +// New tests for increased coverage - commented out to avoid import issues +// import './server/app' +// import './server/utils' +// import './server/functions' +// import './server/config' +// import './server/extensions' +// import './server/publications' +// import './server/schemas' +// import './server/roles' +// import './server/triggers' +// import './server/types' +// import './server/views' diff --git a/test/lib/utils.ts b/test/lib/utils.ts index e4d48fe7..a88391ed 100644 --- a/test/lib/utils.ts +++ b/test/lib/utils.ts @@ -1,9 +1,11 @@ import { afterAll } from 'vitest' import { PostgresMeta } from '../../src/lib' +export const TEST_CONNECTION_STRING = 'postgresql://postgres:postgres@localhost:5432' + export const pgMeta = new PostgresMeta({ max: 1, - connectionString: 'postgresql://postgres:postgres@localhost:5432/postgres', + connectionString: TEST_CONNECTION_STRING, }) afterAll(() => pgMeta.end()) diff --git a/test/publications.test.ts b/test/publications.test.ts new file mode 100644 index 00000000..a6ee0897 --- /dev/null +++ b/test/publications.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/publications', () => { + test('should list publications', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list publications with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/roles.test.ts b/test/roles.test.ts new file mode 100644 index 00000000..d7740804 --- /dev/null +++ b/test/roles.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/roles', () => { + test('should list roles', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list roles with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles?limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/schemas.test.ts b/test/schemas.test.ts new file mode 100644 index 00000000..0faf20dd --- /dev/null +++ b/test/schemas.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/schemas', () => { + test('should list schemas', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list schemas with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/triggers.test.ts b/test/triggers.test.ts new file mode 100644 index 00000000..f8290719 --- /dev/null +++ b/test/triggers.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/triggers', () => { + test('should list triggers', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list triggers with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/types.test.ts b/test/types.test.ts new file mode 100644 index 00000000..6d8bde78 --- /dev/null +++ b/test/types.test.ts @@ -0,0 +1,33 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/types', () => { + test('should list types', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list types with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) +}) diff --git a/test/utils.test.ts b/test/utils.test.ts new file mode 100644 index 00000000..3d70b1a5 --- /dev/null +++ b/test/utils.test.ts @@ -0,0 +1,105 @@ +import { expect, test, describe } from 'vitest' +import { FastifyRequest } from 'fastify' +import { + extractRequestForLogging, + createConnectionConfig, + translateErrorToResponseCode, +} from '../src/server/utils.js' + +describe('server/utils', () => { + describe('extractRequestForLogging', () => { + test('should extract request information for logging', () => { + const mockRequest = { + method: 'GET', + url: '/test', + headers: { + 'user-agent': 'test-agent', + 'x-supabase-info': 'test-info', + }, + query: { param: 'value' }, + } as FastifyRequest + + const result = extractRequestForLogging(mockRequest) + expect(result).toHaveProperty('method') + expect(result).toHaveProperty('url') + expect(result).toHaveProperty('pg') + expect(result).toHaveProperty('opt') + }) + + test('should handle request with minimal properties', () => { + const mockRequest = { + method: 'POST', + url: '/api/test', + headers: {}, + } as FastifyRequest + + const result = extractRequestForLogging(mockRequest) + expect(result.method).toBe('POST') + expect(result.url).toBe('/api/test') + expect(result.pg).toBe('unknown') + }) + }) + + describe('createConnectionConfig', () => { + test('should create connection config from request headers', () => { + const mockRequest = { + headers: { + pg: 'postgresql://user:pass@localhost:5432/db', + 'x-pg-application-name': 'test-app', + }, + } as FastifyRequest + + const result = createConnectionConfig(mockRequest) + expect(result).toHaveProperty('connectionString') + expect(result).toHaveProperty('application_name') + expect(result.connectionString).toBe('postgresql://user:pass@localhost:5432/db') + expect(result.application_name).toBe('test-app') + }) + + test('should handle request without application name', () => { + const mockRequest = { + headers: { + pg: 'postgresql://user:pass@localhost:5432/db', + }, + } as FastifyRequest + + const result = createConnectionConfig(mockRequest) + expect(result).toHaveProperty('connectionString') + expect(result.connectionString).toBe('postgresql://user:pass@localhost:5432/db') + // application_name should have default value if not provided + expect(result.application_name).toBe('postgres-meta 0.0.0-automated') + }) + }) + + describe('translateErrorToResponseCode', () => { + test('should return 504 for connection timeout errors', () => { + const error = { message: 'Connection terminated due to connection timeout' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(504) + }) + + test('should return 503 for too many clients errors', () => { + const error = { message: 'sorry, too many clients already' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(503) + }) + + test('should return 408 for query timeout errors', () => { + const error = { message: 'Query read timeout' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(408) + }) + + test('should return default 400 for other errors', () => { + const error = { message: 'database connection failed' } + const result = translateErrorToResponseCode(error) + expect(result).toBe(400) + }) + + test('should return custom default for other errors', () => { + const error = { message: 'some other error' } + const result = translateErrorToResponseCode(error, 500) + expect(result).toBe(500) + }) + }) +}) diff --git a/test/views.test.ts b/test/views.test.ts new file mode 100644 index 00000000..d713e919 --- /dev/null +++ b/test/views.test.ts @@ -0,0 +1,51 @@ +import { expect, test, describe } from 'vitest' +import { build } from '../src/server/app.js' +import { TEST_CONNECTION_STRING } from './lib/utils.js' + +describe('server/routes/views', () => { + test('should list views', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should list views with query parameters', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views?include_system_schemas=true&limit=5&offset=0', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(200) + expect(Array.isArray(JSON.parse(response.body))).toBe(true) + await app.close() + }) + + test('should return 404 for non-existent view', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/views/1', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "Cannot find a view with ID 1", + } + `) + await app.close() + }) +}) From 913e8795f52ee9931ce03691a9c0e615cf1e845b Mon Sep 17 00:00:00 2001 From: "georgiy.rusanov" Date: Thu, 28 Aug 2025 01:03:35 +0200 Subject: [PATCH 2/3] chore: added more tests --- test/extensions.test.ts | 111 +++++++++++++++++++++++ test/functions.test.ts | 184 ++++++++++++++++++++++++++++++++++++++ test/publications.test.ts | 159 ++++++++++++++++++++++++++++++++ test/roles.test.ts | 152 +++++++++++++++++++++++++++++++ test/schemas.test.ts | 108 ++++++++++++++++++++++ test/triggers.test.ts | 165 ++++++++++++++++++++++++++++++++++ test/types.test.ts | 13 +++ 7 files changed, 892 insertions(+) diff --git a/test/extensions.test.ts b/test/extensions.test.ts index 06149d0d..f6966475 100644 --- a/test/extensions.test.ts +++ b/test/extensions.test.ts @@ -30,4 +30,115 @@ describe('server/routes/extensions', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent extension', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/extensions/non-existent-extension', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create extension, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { name: 'pgcrypto', version: '1.3' }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const retrieveResponse = await app.inject({ + method: 'GET', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { schema: 'public' }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: '/extensions/pgcrypto', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "comment": "cryptographic functions", + "default_version": "1.3", + "installed_version": "1.3", + "name": "pgcrypto", + "schema": "public", + } + `) + + await app.close() + }) + + test('should return 400 for invalid extension name', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/extensions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { name: 'invalid-extension', version: '1.3' }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "could not open extension control file "/usr/share/postgresql/14/extension/invalid-extension.control": No such file or directory", + } + `) + await app.close() + }) }) diff --git a/test/functions.test.ts b/test/functions.test.ts index 3bdec649..8f92fe57 100644 --- a/test/functions.test.ts +++ b/test/functions.test.ts @@ -30,4 +30,188 @@ describe('server/routes/functions', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent function', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/functions/non-existent-function', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create function, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function', + schema: 'public', + language: 'plpgsql', + definition: 'BEGIN RETURN 42; END;', + return_type: 'integer', + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "args": [], + "argument_types": "", + "behavior": "VOLATILE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() + RETURNS integer + LANGUAGE plpgsql + AS $function$BEGIN RETURN 42; END;$function$ + ", + "config_params": null, + "definition": "BEGIN RETURN 42; END;", + "id": expect.any(Number), + "identity_argument_types": "", + "is_set_returning_function": false, + "language": "plpgsql", + "name": "test_function", + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": false, + } + `) + + const { id } = response.json() + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "args": [], + "argument_types": "", + "behavior": "VOLATILE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() + RETURNS integer + LANGUAGE plpgsql + AS $function$BEGIN RETURN 42; END;$function$ + ", + "config_params": null, + "definition": "BEGIN RETURN 42; END;", + "id": ${id}, + "identity_argument_types": "", + "is_set_returning_function": false, + "language": "plpgsql", + "name": "test_function", + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": false, + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function', + schema: 'public', + language: 'plpgsql', + definition: 'BEGIN RETURN 50; END;', + return_type: 'integer', + }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "args": [], + "argument_types": "", + "behavior": "VOLATILE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() + RETURNS integer + LANGUAGE plpgsql + AS $function$BEGIN RETURN 50; END;$function$ + ", + "config_params": null, + "definition": "BEGIN RETURN 50; END;", + "id": ${id}, + "identity_argument_types": "", + "is_set_returning_function": false, + "language": "plpgsql", + "name": "test_function", + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": false, + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/functions/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "args": [], + "argument_types": "", + "behavior": "VOLATILE", + "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() + RETURNS integer + LANGUAGE plpgsql + AS $function$BEGIN RETURN 50; END;$function$ + ", + "config_params": null, + "definition": "BEGIN RETURN 50; END;", + "id": ${id}, + "identity_argument_types": "", + "is_set_returning_function": false, + "language": "plpgsql", + "name": "test_function", + "return_type": "integer", + "return_type_id": 23, + "return_type_relation_id": null, + "schema": "public", + "security_definer": false, + } + `) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/functions', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_function12', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "syntax error at or near "NULL"", + } + `) + }) }) diff --git a/test/publications.test.ts b/test/publications.test.ts index a6ee0897..ad0d9ad7 100644 --- a/test/publications.test.ts +++ b/test/publications.test.ts @@ -30,4 +30,163 @@ describe('server/routes/publications', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent publication', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/publications/non-existent-publication', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create publication, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_publication', + publish_insert: true, + publish_update: true, + publish_delete: true, + publish_truncate: false, + tables: ['users'], + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "id": expect.any(Number), + "name": "test_publication", + "owner": "postgres", + "publish_delete": true, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "id": 16393, + "name": "users", + "schema": "public", + }, + ], + } + `) + + const responseData = response.json() + const { id } = responseData + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "id": ${id}, + "name": "test_publication", + "owner": "postgres", + "publish_delete": true, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "id": 16393, + "name": "users", + "schema": "public", + }, + ], + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + publish_delete: false, + }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "id": ${id}, + "name": "test_publication", + "owner": "postgres", + "publish_delete": false, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "id": 16393, + "name": "users", + "schema": "public", + }, + ], + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/publications/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "id": ${id}, + "name": "test_publication", + "owner": "postgres", + "publish_delete": false, + "publish_insert": true, + "publish_truncate": false, + "publish_update": true, + "tables": [ + { + "id": 16393, + "name": "users", + "schema": "public", + }, + ], + } + `) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/publications', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_publication', + tables: ['non_existent_table'], + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "relation "non_existent_table" does not exist", + } + `) + }) }) diff --git a/test/roles.test.ts b/test/roles.test.ts index d7740804..b94acd50 100644 --- a/test/roles.test.ts +++ b/test/roles.test.ts @@ -30,4 +30,156 @@ describe('server/routes/roles', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent role', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/roles/non-existent-role', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create role, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_role', + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "active_connections": 0, + "can_bypass_rls": false, + "can_create_db": false, + "can_create_role": false, + "can_login": false, + "config": null, + "connection_limit": 100, + "id": expect.any(Number), + "inherit_role": true, + "is_replication_role": false, + "is_superuser": false, + "name": "test_role", + "password": "********", + "valid_until": null, + } + `) + + const { id } = response.json() + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "active_connections": 0, + "can_bypass_rls": false, + "can_create_db": false, + "can_create_role": false, + "can_login": false, + "config": null, + "connection_limit": 100, + "id": ${id}, + "inherit_role": true, + "is_replication_role": false, + "is_superuser": false, + "name": "test_role", + "password": "********", + "valid_until": null, + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_role_updated', + }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "active_connections": 0, + "can_bypass_rls": false, + "can_create_db": false, + "can_create_role": false, + "can_login": false, + "config": null, + "connection_limit": 100, + "id": ${id}, + "inherit_role": true, + "is_replication_role": false, + "is_superuser": false, + "name": "test_role_updated", + "password": "********", + "valid_until": null, + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/roles/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "active_connections": 0, + "can_bypass_rls": false, + "can_create_db": false, + "can_create_role": false, + "can_login": false, + "config": null, + "connection_limit": 100, + "id": ${id}, + "inherit_role": true, + "is_replication_role": false, + "is_superuser": false, + "name": "test_role_updated", + "password": "********", + "valid_until": null, + } + `) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/roles', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'pg_', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "role name "pg_" is reserved", + } + `) + }) }) diff --git a/test/schemas.test.ts b/test/schemas.test.ts index 0faf20dd..30095322 100644 --- a/test/schemas.test.ts +++ b/test/schemas.test.ts @@ -30,4 +30,112 @@ describe('server/routes/schemas', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent schema', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/schemas/non-existent-schema', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create schema, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_schema', + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "id": expect.any(Number), + "name": "test_schema", + "owner": "postgres", + } + `) + + const { id } = response.json() + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "id": ${id}, + "name": "test_schema", + "owner": "postgres", + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_schema_updated', + }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "id": 16886, + "name": "test_schema_updated", + "owner": "postgres", + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/schemas/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "id": ${id}, + "name": "test_schema_updated", + "owner": "postgres", + } + `) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/schemas', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'pg_', + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "unacceptable schema name "pg_"", + } + `) + }) }) diff --git a/test/triggers.test.ts b/test/triggers.test.ts index f8290719..41919de8 100644 --- a/test/triggers.test.ts +++ b/test/triggers.test.ts @@ -30,4 +30,169 @@ describe('server/routes/triggers', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent trigger', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/triggers/non-existent-trigger', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) + + test('should create trigger, retrieve, update, delete', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger1', + table: 'users_audit', + function_name: 'audit_action', + activation: 'AFTER', + events: ['UPDATE'], + }, + }) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchInlineSnapshot(` + { + "activation": "AFTER", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "UPDATE", + ], + "function_args": [], + "function_name": "audit_action", + "function_schema": "public", + "id": expect.any(Number), + "name": "test_trigger1", + "orientation": "STATEMENT", + "schema": "public", + "table": "users_audit", + "table_id": expect.any(Number), + } + `) + + const { id } = response.json() + + const retrieveResponse = await app.inject({ + method: 'GET', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(retrieveResponse.statusCode).toBe(200) + expect(retrieveResponse.json()).toMatchInlineSnapshot(` + { + "activation": "AFTER", + "condition": null, + "enabled_mode": "ORIGIN", + "events": [ + "UPDATE", + ], + "function_args": [], + "function_name": "audit_action", + "function_schema": "public", + "id": ${id}, + "name": "test_trigger1", + "orientation": "STATEMENT", + "schema": "public", + "table": "users_audit", + "table_id": expect.any(Number), + } + `) + + const updateResponse = await app.inject({ + method: 'PATCH', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger1_updated', + enabled_mode: 'DISABLED', + }, + }) + expect(updateResponse.statusCode).toBe(200) + expect(updateResponse.json()).toMatchInlineSnapshot(` + { + "activation": "AFTER", + "condition": null, + "enabled_mode": "DISABLED", + "events": [ + "UPDATE", + ], + "function_args": [], + "function_name": "audit_action", + "function_schema": "public", + "id": ${id}, + "name": "test_trigger1_updated", + "orientation": "STATEMENT", + "schema": "public", + "table": "users_audit", + "table_id": expect.any(Number), + } + `) + + const deleteResponse = await app.inject({ + method: 'DELETE', + url: `/triggers/${id}`, + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(deleteResponse.statusCode).toBe(200) + expect(deleteResponse.json()).toMatchInlineSnapshot(` + { + "activation": "AFTER", + "condition": null, + "enabled_mode": "DISABLED", + "events": [ + "UPDATE", + ], + "function_args": [], + "function_name": "audit_action", + "function_schema": "public", + "id": ${id}, + "name": "test_trigger1_updated", + "orientation": "STATEMENT", + "schema": "public", + "table": "users_audit", + "table_id": expect.any(Number), + } + `) + }) + + test('should return 400 for invalid payload', async () => { + const app = build() + const response = await app.inject({ + method: 'POST', + url: '/triggers', + headers: { + pg: TEST_CONNECTION_STRING, + }, + payload: { + name: 'test_trigger_invalid', + table: 'non_existent_table', + function_name: 'audit_action', + activation: 'AFTER', + events: ['UPDATE'], + }, + }) + expect(response.statusCode).toBe(400) + expect(response.json()).toMatchInlineSnapshot(` + { + "error": "relation "public.non_existent_table" does not exist", + } + `) + }) }) diff --git a/test/types.test.ts b/test/types.test.ts index 6d8bde78..df2af697 100644 --- a/test/types.test.ts +++ b/test/types.test.ts @@ -30,4 +30,17 @@ describe('server/routes/types', () => { expect(Array.isArray(JSON.parse(response.body))).toBe(true) await app.close() }) + + test('should return 404 for non-existent type', async () => { + const app = build() + const response = await app.inject({ + method: 'GET', + url: '/types/non-existent-type', + headers: { + pg: TEST_CONNECTION_STRING, + }, + }) + expect(response.statusCode).toBe(404) + await app.close() + }) }) From 6538bbfb2383221bd9fe5117fe11376a976f2471 Mon Sep 17 00:00:00 2001 From: "georgiy.rusanov" Date: Thu, 28 Aug 2025 01:17:20 +0200 Subject: [PATCH 3/3] chore: try to fix test on ci --- test/functions.test.ts | 182 ++++++++++++++++++-------------------- test/publications.test.ts | 141 ++++++++++++++--------------- test/roles.test.ts | 142 +++++++++++++++-------------- test/schemas.test.ts | 54 ++++++----- test/triggers.test.ts | 142 ++++++++++++++--------------- 5 files changed, 312 insertions(+), 349 deletions(-) diff --git a/test/functions.test.ts b/test/functions.test.ts index 8f92fe57..f37e850e 100644 --- a/test/functions.test.ts +++ b/test/functions.test.ts @@ -61,32 +61,29 @@ describe('server/routes/functions', () => { }, }) expect(response.statusCode).toBe(200) - expect(response.json()).toMatchInlineSnapshot(` - { - "args": [], - "argument_types": "", - "behavior": "VOLATILE", - "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() - RETURNS integer - LANGUAGE plpgsql - AS $function$BEGIN RETURN 42; END;$function$ - ", - "config_params": null, - "definition": "BEGIN RETURN 42; END;", - "id": expect.any(Number), - "identity_argument_types": "", - "is_set_returning_function": false, - "language": "plpgsql", - "name": "test_function", - "return_type": "integer", - "return_type_id": 23, - "return_type_relation_id": null, - "schema": "public", - "security_definer": false, - } - `) + const responseData = response.json() + expect(responseData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 42; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) - const { id } = response.json() + const { id } = responseData const retrieveResponse = await app.inject({ method: 'GET', @@ -96,30 +93,27 @@ describe('server/routes/functions', () => { }, }) expect(retrieveResponse.statusCode).toBe(200) - expect(retrieveResponse.json()).toMatchInlineSnapshot(` - { - "args": [], - "argument_types": "", - "behavior": "VOLATILE", - "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() - RETURNS integer - LANGUAGE plpgsql - AS $function$BEGIN RETURN 42; END;$function$ - ", - "config_params": null, - "definition": "BEGIN RETURN 42; END;", - "id": ${id}, - "identity_argument_types": "", - "is_set_returning_function": false, - "language": "plpgsql", - "name": "test_function", - "return_type": "integer", - "return_type_id": 23, - "return_type_relation_id": null, - "schema": "public", - "security_definer": false, - } - `) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 42; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) const updateResponse = await app.inject({ method: 'PATCH', @@ -136,30 +130,27 @@ describe('server/routes/functions', () => { }, }) expect(updateResponse.statusCode).toBe(200) - expect(updateResponse.json()).toMatchInlineSnapshot(` - { - "args": [], - "argument_types": "", - "behavior": "VOLATILE", - "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() - RETURNS integer - LANGUAGE plpgsql - AS $function$BEGIN RETURN 50; END;$function$ - ", - "config_params": null, - "definition": "BEGIN RETURN 50; END;", - "id": ${id}, - "identity_argument_types": "", - "is_set_returning_function": false, - "language": "plpgsql", - "name": "test_function", - "return_type": "integer", - "return_type_id": 23, - "return_type_relation_id": null, - "schema": "public", - "security_definer": false, - } - `) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 50; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) const deleteResponse = await app.inject({ method: 'DELETE', @@ -169,30 +160,27 @@ describe('server/routes/functions', () => { }, }) expect(deleteResponse.statusCode).toBe(200) - expect(deleteResponse.json()).toMatchInlineSnapshot(` - { - "args": [], - "argument_types": "", - "behavior": "VOLATILE", - "complete_statement": "CREATE OR REPLACE FUNCTION public.test_function() - RETURNS integer - LANGUAGE plpgsql - AS $function$BEGIN RETURN 50; END;$function$ - ", - "config_params": null, - "definition": "BEGIN RETURN 50; END;", - "id": ${id}, - "identity_argument_types": "", - "is_set_returning_function": false, - "language": "plpgsql", - "name": "test_function", - "return_type": "integer", - "return_type_id": 23, - "return_type_relation_id": null, - "schema": "public", - "security_definer": false, - } - `) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + args: [], + argument_types: '', + behavior: 'VOLATILE', + complete_statement: expect.stringContaining( + 'CREATE OR REPLACE FUNCTION public.test_function()' + ), + config_params: null, + definition: 'BEGIN RETURN 50; END;', + id: expect.any(Number), + identity_argument_types: '', + is_set_returning_function: false, + language: 'plpgsql', + name: 'test_function', + return_type: 'integer', + return_type_id: 23, + return_type_relation_id: null, + schema: 'public', + security_definer: false, + }) }) test('should return 400 for invalid payload', async () => { diff --git a/test/publications.test.ts b/test/publications.test.ts index ad0d9ad7..0687c9dd 100644 --- a/test/publications.test.ts +++ b/test/publications.test.ts @@ -62,26 +62,24 @@ describe('server/routes/publications', () => { }, }) expect(response.statusCode).toBe(200) - expect(response.json()).toMatchInlineSnapshot(` - { - "id": expect.any(Number), - "name": "test_publication", - "owner": "postgres", - "publish_delete": true, - "publish_insert": true, - "publish_truncate": false, - "publish_update": true, - "tables": [ - { - "id": 16393, - "name": "users", - "schema": "public", - }, - ], - } - `) - const responseData = response.json() + expect(responseData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: true, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) + const { id } = responseData const retrieveResponse = await app.inject({ @@ -92,24 +90,23 @@ describe('server/routes/publications', () => { }, }) expect(retrieveResponse.statusCode).toBe(200) - expect(retrieveResponse.json()).toMatchInlineSnapshot(` - { - "id": ${id}, - "name": "test_publication", - "owner": "postgres", - "publish_delete": true, - "publish_insert": true, - "publish_truncate": false, - "publish_update": true, - "tables": [ - { - "id": 16393, - "name": "users", - "schema": "public", - }, - ], - } - `) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: true, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) const updateResponse = await app.inject({ method: 'PATCH', @@ -122,24 +119,23 @@ describe('server/routes/publications', () => { }, }) expect(updateResponse.statusCode).toBe(200) - expect(updateResponse.json()).toMatchInlineSnapshot(` - { - "id": ${id}, - "name": "test_publication", - "owner": "postgres", - "publish_delete": false, - "publish_insert": true, - "publish_truncate": false, - "publish_update": true, - "tables": [ - { - "id": 16393, - "name": "users", - "schema": "public", - }, - ], - } - `) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: false, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) const deleteResponse = await app.inject({ method: 'DELETE', @@ -149,24 +145,23 @@ describe('server/routes/publications', () => { }, }) expect(deleteResponse.statusCode).toBe(200) - expect(deleteResponse.json()).toMatchInlineSnapshot(` - { - "id": ${id}, - "name": "test_publication", - "owner": "postgres", - "publish_delete": false, - "publish_insert": true, - "publish_truncate": false, - "publish_update": true, - "tables": [ - { - "id": 16393, - "name": "users", - "schema": "public", - }, - ], - } - `) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + id: expect.any(Number), + name: 'test_publication', + owner: 'postgres', + publish_delete: false, + publish_insert: true, + publish_truncate: false, + publish_update: true, + tables: [ + { + id: expect.any(Number), + name: 'users', + schema: 'public', + }, + ], + }) }) test('should return 400 for invalid payload', async () => { diff --git a/test/roles.test.ts b/test/roles.test.ts index b94acd50..77b98c06 100644 --- a/test/roles.test.ts +++ b/test/roles.test.ts @@ -57,26 +57,25 @@ describe('server/routes/roles', () => { }, }) expect(response.statusCode).toBe(200) - expect(response.json()).toMatchInlineSnapshot(` - { - "active_connections": 0, - "can_bypass_rls": false, - "can_create_db": false, - "can_create_role": false, - "can_login": false, - "config": null, - "connection_limit": 100, - "id": expect.any(Number), - "inherit_role": true, - "is_replication_role": false, - "is_superuser": false, - "name": "test_role", - "password": "********", - "valid_until": null, - } - `) + const responseData = response.json() + expect(responseData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role', + password: '********', + valid_until: null, + }) - const { id } = response.json() + const { id } = responseData const retrieveResponse = await app.inject({ method: 'GET', @@ -86,24 +85,23 @@ describe('server/routes/roles', () => { }, }) expect(retrieveResponse.statusCode).toBe(200) - expect(retrieveResponse.json()).toMatchInlineSnapshot(` - { - "active_connections": 0, - "can_bypass_rls": false, - "can_create_db": false, - "can_create_role": false, - "can_login": false, - "config": null, - "connection_limit": 100, - "id": ${id}, - "inherit_role": true, - "is_replication_role": false, - "is_superuser": false, - "name": "test_role", - "password": "********", - "valid_until": null, - } - `) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role', + password: '********', + valid_until: null, + }) const updateResponse = await app.inject({ method: 'PATCH', @@ -116,24 +114,23 @@ describe('server/routes/roles', () => { }, }) expect(updateResponse.statusCode).toBe(200) - expect(updateResponse.json()).toMatchInlineSnapshot(` - { - "active_connections": 0, - "can_bypass_rls": false, - "can_create_db": false, - "can_create_role": false, - "can_login": false, - "config": null, - "connection_limit": 100, - "id": ${id}, - "inherit_role": true, - "is_replication_role": false, - "is_superuser": false, - "name": "test_role_updated", - "password": "********", - "valid_until": null, - } - `) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role_updated', + password: '********', + valid_until: null, + }) const deleteResponse = await app.inject({ method: 'DELETE', @@ -143,24 +140,23 @@ describe('server/routes/roles', () => { }, }) expect(deleteResponse.statusCode).toBe(200) - expect(deleteResponse.json()).toMatchInlineSnapshot(` - { - "active_connections": 0, - "can_bypass_rls": false, - "can_create_db": false, - "can_create_role": false, - "can_login": false, - "config": null, - "connection_limit": 100, - "id": ${id}, - "inherit_role": true, - "is_replication_role": false, - "is_superuser": false, - "name": "test_role_updated", - "password": "********", - "valid_until": null, - } - `) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + active_connections: 0, + can_bypass_rls: false, + can_create_db: false, + can_create_role: false, + can_login: false, + config: null, + connection_limit: 100, + id: expect.any(Number), + inherit_role: true, + is_replication_role: false, + is_superuser: false, + name: 'test_role_updated', + password: '********', + valid_until: null, + }) }) test('should return 400 for invalid payload', async () => { diff --git a/test/schemas.test.ts b/test/schemas.test.ts index 30095322..73ed73c1 100644 --- a/test/schemas.test.ts +++ b/test/schemas.test.ts @@ -57,15 +57,14 @@ describe('server/routes/schemas', () => { }, }) expect(response.statusCode).toBe(200) - expect(response.json()).toMatchInlineSnapshot(` - { - "id": expect.any(Number), - "name": "test_schema", - "owner": "postgres", - } - `) + const responseData = response.json() + expect(responseData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema', + owner: 'postgres', + }) - const { id } = response.json() + const { id } = responseData const retrieveResponse = await app.inject({ method: 'GET', @@ -75,13 +74,12 @@ describe('server/routes/schemas', () => { }, }) expect(retrieveResponse.statusCode).toBe(200) - expect(retrieveResponse.json()).toMatchInlineSnapshot(` - { - "id": ${id}, - "name": "test_schema", - "owner": "postgres", - } - `) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema', + owner: 'postgres', + }) const updateResponse = await app.inject({ method: 'PATCH', @@ -94,13 +92,12 @@ describe('server/routes/schemas', () => { }, }) expect(updateResponse.statusCode).toBe(200) - expect(updateResponse.json()).toMatchInlineSnapshot(` - { - "id": 16886, - "name": "test_schema_updated", - "owner": "postgres", - } - `) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema_updated', + owner: 'postgres', + }) const deleteResponse = await app.inject({ method: 'DELETE', @@ -110,13 +107,12 @@ describe('server/routes/schemas', () => { }, }) expect(deleteResponse.statusCode).toBe(200) - expect(deleteResponse.json()).toMatchInlineSnapshot(` - { - "id": ${id}, - "name": "test_schema_updated", - "owner": "postgres", - } - `) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + id: expect.any(Number), + name: 'test_schema_updated', + owner: 'postgres', + }) }) test('should return 400 for invalid payload', async () => { diff --git a/test/triggers.test.ts b/test/triggers.test.ts index 41919de8..c537093c 100644 --- a/test/triggers.test.ts +++ b/test/triggers.test.ts @@ -61,27 +61,24 @@ describe('server/routes/triggers', () => { }, }) expect(response.statusCode).toBe(200) - expect(response.json()).toMatchInlineSnapshot(` - { - "activation": "AFTER", - "condition": null, - "enabled_mode": "ORIGIN", - "events": [ - "UPDATE", - ], - "function_args": [], - "function_name": "audit_action", - "function_schema": "public", - "id": expect.any(Number), - "name": "test_trigger1", - "orientation": "STATEMENT", - "schema": "public", - "table": "users_audit", - "table_id": expect.any(Number), - } - `) + const responseData = response.json() + expect(responseData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'ORIGIN', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) - const { id } = response.json() + const { id } = responseData const retrieveResponse = await app.inject({ method: 'GET', @@ -91,25 +88,22 @@ describe('server/routes/triggers', () => { }, }) expect(retrieveResponse.statusCode).toBe(200) - expect(retrieveResponse.json()).toMatchInlineSnapshot(` - { - "activation": "AFTER", - "condition": null, - "enabled_mode": "ORIGIN", - "events": [ - "UPDATE", - ], - "function_args": [], - "function_name": "audit_action", - "function_schema": "public", - "id": ${id}, - "name": "test_trigger1", - "orientation": "STATEMENT", - "schema": "public", - "table": "users_audit", - "table_id": expect.any(Number), - } - `) + const retrieveData = retrieveResponse.json() + expect(retrieveData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'ORIGIN', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) const updateResponse = await app.inject({ method: 'PATCH', @@ -123,25 +117,22 @@ describe('server/routes/triggers', () => { }, }) expect(updateResponse.statusCode).toBe(200) - expect(updateResponse.json()).toMatchInlineSnapshot(` - { - "activation": "AFTER", - "condition": null, - "enabled_mode": "DISABLED", - "events": [ - "UPDATE", - ], - "function_args": [], - "function_name": "audit_action", - "function_schema": "public", - "id": ${id}, - "name": "test_trigger1_updated", - "orientation": "STATEMENT", - "schema": "public", - "table": "users_audit", - "table_id": expect.any(Number), - } - `) + const updateData = updateResponse.json() + expect(updateData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'DISABLED', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1_updated', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) const deleteResponse = await app.inject({ method: 'DELETE', @@ -151,25 +142,22 @@ describe('server/routes/triggers', () => { }, }) expect(deleteResponse.statusCode).toBe(200) - expect(deleteResponse.json()).toMatchInlineSnapshot(` - { - "activation": "AFTER", - "condition": null, - "enabled_mode": "DISABLED", - "events": [ - "UPDATE", - ], - "function_args": [], - "function_name": "audit_action", - "function_schema": "public", - "id": ${id}, - "name": "test_trigger1_updated", - "orientation": "STATEMENT", - "schema": "public", - "table": "users_audit", - "table_id": expect.any(Number), - } - `) + const deleteData = deleteResponse.json() + expect(deleteData).toMatchObject({ + activation: 'AFTER', + condition: null, + enabled_mode: 'DISABLED', + events: ['UPDATE'], + function_args: [], + function_name: 'audit_action', + function_schema: 'public', + id: expect.any(Number), + name: 'test_trigger1_updated', + orientation: 'STATEMENT', + schema: 'public', + table: 'users_audit', + table_id: expect.any(Number), + }) }) test('should return 400 for invalid payload', async () => {