163 lines
5.2 KiB
TypeScript
163 lines
5.2 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { ColumnType } from '@insforge/shared-schemas';
|
|
import { DatabaseTableService } from '../../src/services/database/database-table.service';
|
|
|
|
const queryMock = vi.fn();
|
|
const releaseMock = vi.fn();
|
|
const connectMock = vi.fn();
|
|
|
|
vi.mock('../../src/infra/database/database.manager', () => ({
|
|
DatabaseManager: {
|
|
getInstance: vi.fn(() => ({
|
|
getPool: vi.fn(() => ({
|
|
connect: connectMock,
|
|
query: queryMock,
|
|
})),
|
|
})),
|
|
},
|
|
}));
|
|
|
|
describe('DatabaseTableService project_admin DDL context', () => {
|
|
let service: DatabaseTableService;
|
|
let tableExists = false;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
tableExists = false;
|
|
service = DatabaseTableService.getInstance();
|
|
|
|
connectMock.mockResolvedValue({
|
|
query: queryMock,
|
|
release: releaseMock,
|
|
});
|
|
|
|
queryMock.mockImplementation(async (sql: string) => {
|
|
if (sql.includes('SELECT EXISTS')) {
|
|
return { rows: [{ exists: tableExists }], rowCount: 1 };
|
|
}
|
|
|
|
if (sql.includes('FROM information_schema.columns')) {
|
|
return {
|
|
rows: [
|
|
{
|
|
column_name: 'id',
|
|
data_type: 'uuid',
|
|
udt_name: 'uuid',
|
|
is_nullable: 'NO',
|
|
column_default: 'gen_random_uuid()',
|
|
character_maximum_length: null,
|
|
},
|
|
{
|
|
column_name: 'title',
|
|
data_type: 'text',
|
|
udt_name: 'text',
|
|
is_nullable: 'YES',
|
|
column_default: null,
|
|
character_maximum_length: null,
|
|
},
|
|
],
|
|
rowCount: 2,
|
|
};
|
|
}
|
|
|
|
if (sql.includes("tc.constraint_type = 'FOREIGN KEY'")) {
|
|
return { rows: [], rowCount: 0 };
|
|
}
|
|
|
|
if (sql.includes("AND constraint_type = 'PRIMARY KEY'")) {
|
|
return { rows: [{ column_name: 'id' }], rowCount: 1 };
|
|
}
|
|
|
|
if (sql.includes("AND tc.constraint_type = 'UNIQUE'")) {
|
|
return { rows: [], rowCount: 0 };
|
|
}
|
|
|
|
if (sql.includes('COUNT(*) as row_count')) {
|
|
return { rows: [{ row_count: '0' }], rowCount: 1 };
|
|
}
|
|
|
|
return { rows: [], rowCount: 0 };
|
|
});
|
|
});
|
|
|
|
it('creates dashboard tables as project_admin', async () => {
|
|
await service.createTable(
|
|
'public',
|
|
'posts',
|
|
[{ columnName: 'title', type: ColumnType.STRING, isNullable: false }],
|
|
[],
|
|
true
|
|
);
|
|
|
|
const sqlCalls = queryMock.mock.calls.map(([sql]) => sql as string);
|
|
const setRoleIndex = sqlCalls.indexOf('SET LOCAL ROLE project_admin');
|
|
const createTableIndex = sqlCalls.findIndex((sql) =>
|
|
sql.includes('CREATE TABLE "public"."posts"')
|
|
);
|
|
const resetRoleIndex = sqlCalls.indexOf('RESET ROLE');
|
|
const commitIndex = sqlCalls.indexOf('COMMIT');
|
|
|
|
expect(setRoleIndex).toBeGreaterThan(-1);
|
|
expect(createTableIndex).toBeGreaterThan(setRoleIndex);
|
|
expect(resetRoleIndex).toBeGreaterThan(createTableIndex);
|
|
expect(commitIndex).toBeGreaterThan(resetRoleIndex);
|
|
});
|
|
|
|
it('updates dashboard table schema as project_admin', async () => {
|
|
tableExists = true;
|
|
|
|
await service.updateTableSchema('public', 'posts', {
|
|
addColumns: [{ columnName: 'summary', type: ColumnType.STRING, isNullable: true }],
|
|
});
|
|
|
|
const sqlCalls = queryMock.mock.calls.map(([sql]) => sql as string);
|
|
const setRoleIndex = sqlCalls.indexOf('SET LOCAL ROLE project_admin');
|
|
const alterTableIndex = sqlCalls.findIndex(
|
|
(sql) => sql.includes('ALTER TABLE "public"."posts"') && sql.includes('ADD COLUMN "summary"')
|
|
);
|
|
const resetRoleIndex = sqlCalls.indexOf('RESET ROLE');
|
|
const commitIndex = sqlCalls.indexOf('COMMIT');
|
|
|
|
expect(setRoleIndex).toBeGreaterThan(-1);
|
|
expect(alterTableIndex).toBeGreaterThan(setRoleIndex);
|
|
expect(resetRoleIndex).toBeGreaterThan(alterTableIndex);
|
|
expect(commitIndex).toBeGreaterThan(resetRoleIndex);
|
|
});
|
|
|
|
it('deletes dashboard tables as project_admin', async () => {
|
|
await service.deleteTable('public', 'posts');
|
|
|
|
const sqlCalls = queryMock.mock.calls.map(([sql]) => sql as string);
|
|
const setRoleIndex = sqlCalls.indexOf('SET LOCAL ROLE project_admin');
|
|
const dropTableIndex = sqlCalls.findIndex((sql) =>
|
|
sql.includes('DROP TABLE IF EXISTS "public"."posts" CASCADE')
|
|
);
|
|
const resetRoleIndex = sqlCalls.indexOf('RESET ROLE');
|
|
const commitIndex = sqlCalls.indexOf('COMMIT');
|
|
|
|
expect(setRoleIndex).toBeGreaterThan(-1);
|
|
expect(dropTableIndex).toBeGreaterThan(setRoleIndex);
|
|
expect(resetRoleIndex).toBeGreaterThan(dropTableIndex);
|
|
expect(commitIndex).toBeGreaterThan(resetRoleIndex);
|
|
});
|
|
|
|
it('delegates protected-schema create attempts to project_admin privileges', async () => {
|
|
await service.createTable(
|
|
'auth',
|
|
'users_copy',
|
|
[{ columnName: 'email', type: ColumnType.STRING, isNullable: false }],
|
|
[],
|
|
true
|
|
);
|
|
|
|
const sqlCalls = queryMock.mock.calls.map(([sql]) => sql as string);
|
|
const setRoleIndex = sqlCalls.indexOf('SET LOCAL ROLE project_admin');
|
|
const createTableIndex = sqlCalls.findIndex((sql) =>
|
|
sql.includes('CREATE TABLE "auth"."users_copy"')
|
|
);
|
|
|
|
expect(setRoleIndex).toBeGreaterThan(-1);
|
|
expect(createTableIndex).toBeGreaterThan(setRoleIndex);
|
|
});
|
|
});
|