Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 164 additions & 0 deletions pgpm/core/__tests__/roles/roles-sql-generators.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import {
generateCreateBaseRolesSQL,
generateCreateUserSQL,
generateCreateTestUsersSQL,
generateRemoveUserSQL
} from '../../src/roles';

describe('Role SQL Generators - Input Validation', () => {
describe('generateCreateBaseRolesSQL', () => {
it('should throw an error when roles is undefined', () => {
expect(() => {
generateCreateBaseRolesSQL(undefined as any);
}).toThrow('generateCreateBaseRolesSQL: roles parameter is undefined');
});

it('should throw an error when roles is null', () => {
expect(() => {
generateCreateBaseRolesSQL(null as any);
}).toThrow('generateCreateBaseRolesSQL: roles parameter is undefined');
});

it('should throw an error when roles.anonymous is missing', () => {
expect(() => {
generateCreateBaseRolesSQL({
authenticated: 'authenticated',
administrator: 'administrator'
});
}).toThrow('generateCreateBaseRolesSQL: roles is missing required properties');
});

it('should throw an error when roles.authenticated is missing', () => {
expect(() => {
generateCreateBaseRolesSQL({
anonymous: 'anonymous',
administrator: 'administrator'
});
}).toThrow('generateCreateBaseRolesSQL: roles is missing required properties');
});

it('should throw an error when roles.administrator is missing', () => {
expect(() => {
generateCreateBaseRolesSQL({
anonymous: 'anonymous',
authenticated: 'authenticated'
});
}).toThrow('generateCreateBaseRolesSQL: roles is missing required properties');
});

it('should generate valid SQL when all roles are provided', () => {
const sql = generateCreateBaseRolesSQL({
anonymous: 'anon',
authenticated: 'auth',
administrator: 'admin'
});
expect(sql).toContain('anon');
expect(sql).toContain('auth');
expect(sql).toContain('admin');
expect(sql).toContain('CREATE ROLE');
});
});

describe('generateCreateUserSQL', () => {
it('should throw an error when roles is undefined', () => {
expect(() => {
generateCreateUserSQL('testuser', 'testpass', undefined as any);
}).toThrow('generateCreateUserSQL: roles parameter is undefined');
});

it('should throw an error when roles.anonymous is missing', () => {
expect(() => {
generateCreateUserSQL('testuser', 'testpass', {
authenticated: 'authenticated'
});
}).toThrow('generateCreateUserSQL: roles is missing required properties');
});

it('should generate valid SQL when all required roles are provided', () => {
const sql = generateCreateUserSQL('testuser', 'testpass', {
anonymous: 'anon',
authenticated: 'auth'
});
expect(sql).toContain('testuser');
expect(sql).toContain('anon');
expect(sql).toContain('auth');
});
});

describe('generateCreateTestUsersSQL', () => {
const validRoles = {
anonymous: 'anon',
authenticated: 'auth',
administrator: 'admin'
};

const validConnections = {
app: { user: 'app_user', password: 'app_pass' },
admin: { user: 'admin_user', password: 'admin_pass' }
};

it('should throw an error when roles is undefined', () => {
expect(() => {
generateCreateTestUsersSQL(undefined as any, validConnections);
}).toThrow('generateCreateTestUsersSQL: roles parameter is undefined');
});

it('should throw an error when connections is undefined', () => {
expect(() => {
generateCreateTestUsersSQL(validRoles, undefined as any);
}).toThrow('generateCreateTestUsersSQL: connections parameter is undefined');
});

it('should throw an error when connections.app is missing', () => {
expect(() => {
generateCreateTestUsersSQL(validRoles, {
admin: { user: 'admin_user', password: 'admin_pass' }
});
}).toThrow('generateCreateTestUsersSQL: connections is missing required properties');
});

it('should throw an error when connections.admin is missing', () => {
expect(() => {
generateCreateTestUsersSQL(validRoles, {
app: { user: 'app_user', password: 'app_pass' }
});
}).toThrow('generateCreateTestUsersSQL: connections is missing required properties');
});

it('should generate valid SQL when all parameters are provided', () => {
const sql = generateCreateTestUsersSQL(validRoles, validConnections);
expect(sql).toContain('app_user');
expect(sql).toContain('admin_user');
expect(sql).toContain('anon');
expect(sql).toContain('auth');
expect(sql).toContain('admin');
});
});

describe('generateRemoveUserSQL', () => {
it('should throw an error when roles is undefined', () => {
expect(() => {
generateRemoveUserSQL('testuser', undefined as any);
}).toThrow('generateRemoveUserSQL: roles parameter is undefined');
});

it('should throw an error when roles.anonymous is missing', () => {
expect(() => {
generateRemoveUserSQL('testuser', {
authenticated: 'authenticated'
});
}).toThrow('generateRemoveUserSQL: roles is missing required properties');
});

it('should generate valid SQL when all required roles are provided', () => {
const sql = generateRemoveUserSQL('testuser', {
anonymous: 'anon',
authenticated: 'auth'
});
expect(sql).toContain('testuser');
expect(sql).toContain('anon');
expect(sql).toContain('auth');
expect(sql).toContain('DROP ROLE');
});
});
});
87 changes: 75 additions & 12 deletions pgpm/core/src/roles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,27 @@ function sqlLiteral(value: string): string {
* Generate SQL to create base roles (anonymous, authenticated, administrator).
* Callers should use getConnEnvOptions() from @pgpmjs/env to get merged values.
* @param roles - Role mapping from getConnEnvOptions().roles!
* @throws Error if roles is undefined or missing required properties
*/
export function generateCreateBaseRolesSQL(roles: RoleMapping): string {
if (!roles) {
throw new Error(
'generateCreateBaseRolesSQL: roles parameter is undefined. ' +
'Ensure getConnEnvOptions().roles is defined. ' +
'Check that pgpm.config.js or pgpm.json does not set db.roles to undefined.'
);
}
if (!roles.anonymous || !roles.authenticated || !roles.administrator) {
throw new Error(
'generateCreateBaseRolesSQL: roles is missing required properties. ' +
`Got: anonymous=${roles.anonymous}, authenticated=${roles.authenticated}, administrator=${roles.administrator}. ` +
'Ensure all role names are defined in your configuration.'
);
}
const r = {
anonymous: roles.anonymous!,
authenticated: roles.authenticated!,
administrator: roles.administrator!
anonymous: roles.anonymous,
authenticated: roles.authenticated,
administrator: roles.administrator
};

return `
Expand Down Expand Up @@ -101,9 +116,21 @@ export function generateCreateUserSQL(
roles: RoleMapping,
useLocksForRoles = false
): string {
if (!roles) {
throw new Error(
'generateCreateUserSQL: roles parameter is undefined. ' +
'Ensure getConnEnvOptions().roles is defined.'
);
}
if (!roles.anonymous || !roles.authenticated) {
throw new Error(
'generateCreateUserSQL: roles is missing required properties. ' +
`Got: anonymous=${roles.anonymous}, authenticated=${roles.authenticated}.`
);
}
const r = {
anonymous: roles.anonymous!,
authenticated: roles.authenticated!
anonymous: roles.anonymous,
authenticated: roles.authenticated
};
const lockStatement = useLocksForRoles
? `PERFORM pg_advisory_xact_lock(42, hashtext(v_username));`
Expand Down Expand Up @@ -201,14 +228,38 @@ export function generateCreateTestUsersSQL(
roles: RoleMapping,
connections: TestUserCredentials
): string {
if (!roles) {
throw new Error(
'generateCreateTestUsersSQL: roles parameter is undefined. ' +
'Ensure getConnEnvOptions().roles is defined.'
);
}
if (!roles.anonymous || !roles.authenticated || !roles.administrator) {
throw new Error(
'generateCreateTestUsersSQL: roles is missing required properties. ' +
`Got: anonymous=${roles.anonymous}, authenticated=${roles.authenticated}, administrator=${roles.administrator}.`
);
}
if (!connections) {
throw new Error(
'generateCreateTestUsersSQL: connections parameter is undefined. ' +
'Ensure getConnEnvOptions().connections is defined.'
);
}
if (!connections.app?.user || !connections.app?.password || !connections.admin?.user || !connections.admin?.password) {
throw new Error(
'generateCreateTestUsersSQL: connections is missing required properties. ' +
'Ensure app.user, app.password, admin.user, and admin.password are defined.'
);
}
const r = {
anonymous: roles.anonymous!,
authenticated: roles.authenticated!,
administrator: roles.administrator!
anonymous: roles.anonymous,
authenticated: roles.authenticated,
administrator: roles.administrator
};
const users = {
app: { user: connections.app!.user!, password: connections.app!.password! },
admin: { user: connections.admin!.user!, password: connections.admin!.password! }
app: { user: connections.app.user, password: connections.app.password },
admin: { user: connections.admin.user, password: connections.admin.password }
};

return `
Expand Down Expand Up @@ -388,9 +439,21 @@ export function generateRemoveUserSQL(
roles: RoleMapping,
useLocksForRoles = false
): string {
if (!roles) {
throw new Error(
'generateRemoveUserSQL: roles parameter is undefined. ' +
'Ensure getConnEnvOptions().roles is defined.'
);
}
if (!roles.anonymous || !roles.authenticated) {
throw new Error(
'generateRemoveUserSQL: roles is missing required properties. ' +
`Got: anonymous=${roles.anonymous}, authenticated=${roles.authenticated}.`
);
}
const r = {
anonymous: roles.anonymous!,
authenticated: roles.authenticated!
anonymous: roles.anonymous,
authenticated: roles.authenticated
};
const lockStatement = useLocksForRoles
? `PERFORM pg_advisory_xact_lock(42, hashtext(v_username));`
Expand Down
103 changes: 103 additions & 0 deletions pgpm/env/__tests__/merge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { getConnEnvOptions } from '../src/merge';
import { pgpmDefaults } from '@pgpmjs/types';

describe('getConnEnvOptions', () => {
describe('roles resolution', () => {
it('should always return roles with default values when no overrides provided', () => {
const result = getConnEnvOptions();

expect(result.roles).toBeDefined();
expect(result.roles?.anonymous).toBe('anonymous');
expect(result.roles?.authenticated).toBe('authenticated');
expect(result.roles?.administrator).toBe('administrator');
});

it('should preserve default roles even when roles is explicitly undefined in overrides', () => {
const result = getConnEnvOptions({ roles: undefined });

expect(result.roles).toBeDefined();
expect(result.roles?.anonymous).toBe('anonymous');
expect(result.roles?.authenticated).toBe('authenticated');
expect(result.roles?.administrator).toBe('administrator');
});

it('should allow overriding individual role names while preserving others', () => {
const result = getConnEnvOptions({
roles: {
anonymous: 'custom_anon'
}
});

expect(result.roles?.anonymous).toBe('custom_anon');
expect(result.roles?.authenticated).toBe('authenticated');
expect(result.roles?.administrator).toBe('administrator');
});

it('should allow overriding all role names', () => {
const result = getConnEnvOptions({
roles: {
anonymous: 'custom_anon',
authenticated: 'custom_auth',
administrator: 'custom_admin'
}
});

expect(result.roles?.anonymous).toBe('custom_anon');
expect(result.roles?.authenticated).toBe('custom_auth');
expect(result.roles?.administrator).toBe('custom_admin');
});
});

describe('connections resolution', () => {
it('should always return connections with default values when no overrides provided', () => {
const result = getConnEnvOptions();

expect(result.connections).toBeDefined();
expect(result.connections?.app?.user).toBe('app_user');
expect(result.connections?.app?.password).toBe('app_password');
expect(result.connections?.admin?.user).toBe('app_admin');
expect(result.connections?.admin?.password).toBe('admin_password');
});

it('should preserve default connections even when connections is explicitly undefined', () => {
const result = getConnEnvOptions({ connections: undefined });

expect(result.connections).toBeDefined();
expect(result.connections?.app?.user).toBe('app_user');
expect(result.connections?.admin?.user).toBe('app_admin');
});

it('should allow overriding individual connection properties while preserving others', () => {
const result = getConnEnvOptions({
connections: {
app: {
user: 'custom_app_user'
}
}
});

expect(result.connections?.app?.user).toBe('custom_app_user');
expect(result.connections?.app?.password).toBe('app_password');
expect(result.connections?.admin?.user).toBe('app_admin');
});
});

describe('other properties', () => {
it('should preserve other db properties from defaults', () => {
const result = getConnEnvOptions();

expect(result.rootDb).toBe(pgpmDefaults.db?.rootDb);
expect(result.prefix).toBe(pgpmDefaults.db?.prefix);
});

it('should allow overriding other db properties', () => {
const result = getConnEnvOptions({
rootDb: 'custom_root',
prefix: 'custom-'
});

expect(result.rootDb).toBe('custom_root');
expect(result.prefix).toBe('custom-');
});
});
});
Loading
Loading