diff --git a/src/lib/PostgresMetaPolicies.ts b/src/lib/PostgresMetaPolicies.ts index fa476c12..fa00597d 100644 --- a/src/lib/PostgresMetaPolicies.ts +++ b/src/lib/PostgresMetaPolicies.ts @@ -112,6 +112,9 @@ export default class PostgresMetaPolicies { command?: string roles?: string[] }): Promise> { + if (!table || !name) { + return { data: null, error: { message: 'Missing required name or table parameter' } } + } const definitionClause = definition === undefined ? '' : `USING (${definition})` const checkClause = check === undefined ? '' : `WITH CHECK (${check})` const sql = ` diff --git a/test/index.test.ts b/test/index.test.ts index 9a315921..caa43983 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -16,10 +16,19 @@ import './lib/types' import './lib/version' import './lib/views' import './server/column-privileges' +import './server/config' +import './server/extensions' +import './server/format' +import './server/functions' +import './server/generators' import './server/indexes' import './server/materialized-views' +import './server/policies' +import './server/publications' import './server/query' import './server/ssl' import './server/table-privileges' +import './server/triggers' +import './server/types' import './server/typegen' import './server/result-size-limit' diff --git a/test/server/config.ts b/test/server/config.ts new file mode 100644 index 00000000..b683c097 --- /dev/null +++ b/test/server/config.ts @@ -0,0 +1,23 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('config version endpoint', async () => { + const res = await app.inject({ + method: 'GET', + path: '/config/version', + }) + expect(res.statusCode).toBe(200) + const data = res.json() + expect(data).toHaveProperty('version') + expect(typeof data.version).toBe('string') + // Accept any version string format + expect(data.version).toContain('PostgreSQL') +}) + +test('config with invalid endpoint', async () => { + const res = await app.inject({ + method: 'GET', + path: '/config/invalid', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/extensions.ts b/test/server/extensions.ts new file mode 100644 index 00000000..f4110415 --- /dev/null +++ b/test/server/extensions.ts @@ -0,0 +1,43 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('extension list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/extensions?limit=5', + }) + expect(res.statusCode).toBe(200) + const extensions = res.json() + expect(Array.isArray(extensions)).toBe(true) + expect(extensions.length).toBeLessThanOrEqual(5) +}) + +test('extension with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/extensions/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('create extension with invalid name', async () => { + const res = await app.inject({ + method: 'POST', + path: '/extensions', + payload: { + name: 'invalid_extension_name_that_doesnt_exist', + schema: 'public', + version: '1.0', + cascade: false, + }, + }) + expect(res.statusCode).toBe(400) +}) + +test('delete extension with invalid id', async () => { + const res = await app.inject({ + method: 'DELETE', + path: '/extensions/99999999', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/format.ts b/test/server/format.ts new file mode 100644 index 00000000..78704aa0 --- /dev/null +++ b/test/server/format.ts @@ -0,0 +1,89 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('format SQL query', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: { query: "SELECT id,name FROM users WHERE status='ACTIVE'" }, + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + const formattedQuery = res.body + expect(formattedQuery).toMatchInlineSnapshot(` + "SELECT + id, + name + FROM + users + WHERE + status = 'ACTIVE' + " + `) +}) + +test('format complex SQL query', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: { + query: + "SELECT u.id, u.name, p.title, p.created_at FROM users u JOIN posts p ON u.id = p.user_id WHERE u.status = 'ACTIVE' AND p.published = true ORDER BY p.created_at DESC LIMIT 10", + }, + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + expect(res.body).toMatchInlineSnapshot(` + "SELECT + u.id, + u.name, + p.title, + p.created_at + FROM + users u + JOIN posts p ON u.id = p.user_id + WHERE + u.status = 'ACTIVE' + AND p.published = true + ORDER BY + p.created_at DESC + LIMIT + 10 + " + `) +}) + +test('format invalid SQL query', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: { query: 'SELECT FROM WHERE;' }, + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + expect(res.body).toMatchInlineSnapshot(` + "SELECT + FROM + WHERE; + " + `) +}) + +// TODO(andrew): Those should return 400 error code for invalid parameter +test('format empty query', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: { query: '' }, + }) + expect(res.statusCode).toBe(500) +}) + +test('format with missing query parameter', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query/format', + payload: {}, + }) + expect(res.statusCode).toBe(500) +}) diff --git a/test/server/functions.ts b/test/server/functions.ts new file mode 100644 index 00000000..010372d6 --- /dev/null +++ b/test/server/functions.ts @@ -0,0 +1,81 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('function list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/functions?limit=5', + }) + expect(res.statusCode).toBe(200) + const functions = res.json() + expect(Array.isArray(functions)).toBe(true) + expect(functions.length).toBeLessThanOrEqual(5) +}) + +test('function list with specific included schema', async () => { + const res = await app.inject({ + method: 'GET', + path: '/functions?includedSchemas=public', + }) + expect(res.statusCode).toBe(200) + const functions = res.json() + expect(Array.isArray(functions)).toBe(true) + // All functions should be in the public schema + functions.forEach((func) => { + expect(func.schema).toBe('public') + }) +}) + +test('function list exclude system schemas', async () => { + const res = await app.inject({ + method: 'GET', + path: '/functions?includeSystemSchemas=false', + }) + expect(res.statusCode).toBe(200) + const functions = res.json() + expect(Array.isArray(functions)).toBe(true) + // No functions should be in pg_ schemas + functions.forEach((func) => { + expect(func.schema).not.toMatch(/^pg_/) + }) +}) + +test('function with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/functions/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('create function with invalid arguments', async () => { + const res = await app.inject({ + method: 'POST', + path: '/functions', + payload: { + name: 'invalid_function', + schema: 'public', + // Missing required args + }, + }) + expect(res.statusCode).toBe(400) +}) + +test('update function with invalid id', async () => { + const res = await app.inject({ + method: 'PATCH', + path: '/functions/99999999', + payload: { + name: 'renamed_function', + }, + }) + expect(res.statusCode).toBe(404) +}) + +test('delete function with invalid id', async () => { + const res = await app.inject({ + method: 'DELETE', + path: '/functions/99999999', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/generators.ts b/test/server/generators.ts new file mode 100644 index 00000000..b6272a0b --- /dev/null +++ b/test/server/generators.ts @@ -0,0 +1,51 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('typescript generator route', async () => { + const res = await app.inject({ + method: 'GET', + path: '/generators/typescript', + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + expect(res.body).contain('public') +}) + +test('go generator route', async () => { + const res = await app.inject({ + method: 'GET', + path: '/generators/go', + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + expect(res.body).toBeTruthy() +}) + +test('swift generator route', async () => { + const res = await app.inject({ + method: 'GET', + path: '/generators/swift', + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + expect(res.body).toBeTruthy() +}) + +test('generator routes with includedSchemas parameter', async () => { + const res = await app.inject({ + method: 'GET', + path: '/generators/typescript?included_schemas=private', + }) + expect(res.statusCode).toBe(200) + expect(res.headers['content-type']).toContain('text/plain') + // the only schema is excluded database should be empty + expect(res.body).toContain('Database = {}') +}) + +test('invalid generator route', async () => { + const res = await app.inject({ + method: 'GET', + path: '/generators/openapi', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/policies.ts b/test/server/policies.ts new file mode 100644 index 00000000..5838bc87 --- /dev/null +++ b/test/server/policies.ts @@ -0,0 +1,71 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('policy list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/policies?limit=5', + }) + expect(res.statusCode).toBe(200) + const policies = res.json() + expect(Array.isArray(policies)).toBe(true) + expect(policies.length).toBeLessThanOrEqual(5) +}) + +test('policy list with specific included schema', async () => { + const res = await app.inject({ + method: 'GET', + path: '/policies?included_schema=public', + }) + expect(res.statusCode).toBe(200) + const policies = res.json() + expect(Array.isArray(policies)).toBe(true) + // All policies should be in the public schema + policies.forEach((policy) => { + expect(policy.schema).toBe('public') + }) +}) + +test('policy with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/policies/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('create policy with missing required field', async () => { + const res = await app.inject({ + method: 'POST', + path: '/policies', + payload: { + name: 'test_policy', + schema: 'public', + // Missing required table field + definition: 'true', + check: 'true', + action: 'SELECT', + command: 'PERMISSIVE', + }, + }) + expect(res.statusCode).toBe(400) +}) + +test('update policy with invalid id', async () => { + const res = await app.inject({ + method: 'PATCH', + path: '/policies/99999999', + payload: { + name: 'renamed_policy', + }, + }) + expect(res.statusCode).toBe(404) +}) + +test('delete policy with invalid id', async () => { + const res = await app.inject({ + method: 'DELETE', + path: '/policies/99999999', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/publications.ts b/test/server/publications.ts new file mode 100644 index 00000000..749d6640 --- /dev/null +++ b/test/server/publications.ts @@ -0,0 +1,74 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('publication list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/publications?limit=5', + }) + expect(res.statusCode).toBe(200) + const publications = res.json() + expect(Array.isArray(publications)).toBe(true) + expect(publications.length).toBeLessThanOrEqual(5) +}) + +test('publication with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/publications/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('create publication with invalid options', async () => { + const res = await app.inject({ + method: 'POST', + path: '/publications', + payload: { + name: 'test_publication', + publish_insert: 'invalid', // Should be boolean but seems to be converted automatically + publish_update: true, + publish_delete: true, + publish_truncate: true, + tables: ['public.users'], + }, + }) + // API accepts invalid type and converts it + // TODO: This should error out with invalid parameter + expect(res.statusCode).toBe(200) +}) + +test('create publication with empty name', async () => { + const res = await app.inject({ + method: 'POST', + path: '/publications', + payload: { + name: '', + publish_insert: true, + publish_update: true, + publish_delete: true, + publish_truncate: true, + tables: ['public.users'], + }, + }) + expect(res.statusCode).toBe(400) +}) + +test('update publication with invalid id', async () => { + const res = await app.inject({ + method: 'PATCH', + path: '/publications/99999999', + payload: { + name: 'renamed_publication', + }, + }) + expect(res.statusCode).toBe(404) +}) + +test('delete publication with invalid id', async () => { + const res = await app.inject({ + method: 'DELETE', + path: '/publications/99999999', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/triggers.ts b/test/server/triggers.ts new file mode 100644 index 00000000..b930d567 --- /dev/null +++ b/test/server/triggers.ts @@ -0,0 +1,75 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('trigger list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/triggers?limit=5', + }) + expect(res.statusCode).toBe(200) + const triggers = res.json() + expect(Array.isArray(triggers)).toBe(true) + expect(triggers.length).toBeLessThanOrEqual(5) +}) + +test('trigger list with specific included schema', async () => { + const res = await app.inject({ + method: 'GET', + path: '/triggers?includedSchemas=public', + }) + expect(res.statusCode).toBe(200) + const triggers = res.json() + expect(Array.isArray(triggers)).toBe(true) + // All triggers should be in the public schema + triggers.forEach((trigger) => { + expect(trigger.schema).toBe('public') + }) +}) + +test('trigger with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/triggers/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('create trigger with invalid parameters', async () => { + const res = await app.inject({ + method: 'POST', + path: '/triggers', + payload: { + name: 'test_trigger', + schema: 'public', + table: 'non_existent_table', + function_schema: 'public', + function_name: 'test_trigger_function', + function_args: [], + activation: 'BEFORE', + events: ['INSERT'], + orientation: 'ROW', + condition: null, + }, + }) + // Should fail because table doesn't exist + expect(res.statusCode).toBe(400) +}) + +test('update trigger with invalid id', async () => { + const res = await app.inject({ + method: 'PATCH', + path: '/triggers/99999999', + payload: { + enabled: false, + }, + }) + expect(res.statusCode).toBe(404) +}) + +test('delete trigger with invalid id', async () => { + const res = await app.inject({ + method: 'DELETE', + path: '/triggers/99999999', + }) + expect(res.statusCode).toBe(404) +}) diff --git a/test/server/types.ts b/test/server/types.ts new file mode 100644 index 00000000..8ae12beb --- /dev/null +++ b/test/server/types.ts @@ -0,0 +1,62 @@ +import { expect, test } from 'vitest' +import { app } from './utils' + +test('type list filtering', async () => { + const res = await app.inject({ + method: 'GET', + path: '/types?limit=5', + }) + expect(res.statusCode).toBe(200) + const types = res.json() + expect(Array.isArray(types)).toBe(true) + expect(types.length).toBeLessThanOrEqual(5) +}) + +test('type list with specific included schema', async () => { + const res = await app.inject({ + method: 'GET', + path: '/types?includedSchemas=public', + }) + expect(res.statusCode).toBe(200) + const types = res.json() + expect(Array.isArray(types)).toBe(true) + // All types should be in the public schema + types.forEach((type) => { + expect(type.schema).toBe('public') + }) +}) + +test('type list excluding array types', async () => { + const res = await app.inject({ + method: 'GET', + path: '/types?includeArrayTypes=false', + }) + expect(res.statusCode).toBe(200) + const types = res.json() + expect(Array.isArray(types)).toBe(true) + // Should not include array types + const arrayTypes = types.filter((type) => type.name.startsWith('_')) + expect(arrayTypes.length).toBe(0) +}) + +test('type with invalid id', async () => { + const res = await app.inject({ + method: 'GET', + path: '/types/99999999', + }) + expect(res.statusCode).toBe(404) +}) + +test('type with enum values', async () => { + // Find an enum type first + const listRes = await app.inject({ + method: 'GET', + path: '/types', + }) + expect(listRes.statusCode).toBe(200) + const types = listRes.json() + const enumType = types.find((t) => t.name === 'meme_status') + + expect(Array.isArray(enumType.enums)).toBe(true) + expect(enumType.enums.length).toBeGreaterThan(0) +}) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy