Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/run-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
env: {}
- package: pgpm/core
env: {}
- package: pgpm/env
env: {}
- package: packages/cli
env: {}
- package: packages/client
Expand Down
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
Loading