first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,475 @@
|
||||
import {
|
||||
createTestMigrationContext,
|
||||
initDbUpToMigration,
|
||||
runSingleMigration,
|
||||
undoLastSingleMigration,
|
||||
type TestMigrationContext,
|
||||
} from '@n8n/backend-test-utils';
|
||||
import { DbConnection } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import { DataSource } from '@n8n/typeorm';
|
||||
|
||||
const MIGRATION_NAME = 'UniqueRoleNames1760020838000';
|
||||
|
||||
interface RoleData {
|
||||
slug: string;
|
||||
displayName: string;
|
||||
createdAt: Date;
|
||||
systemRole?: boolean;
|
||||
roleType?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
interface RoleRow {
|
||||
slug: string;
|
||||
displayName: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
describe('UniqueRoleNames Migration', () => {
|
||||
let dataSource: DataSource;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Initialize DB connection without running migrations
|
||||
const dbConnection = Container.get(DbConnection);
|
||||
await dbConnection.init();
|
||||
|
||||
dataSource = Container.get(DataSource);
|
||||
|
||||
// Clear database to start with clean slate
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
await context.queryRunner.clearDatabase();
|
||||
await context.queryRunner.release();
|
||||
|
||||
// Run migrations up to (but not including) target migration
|
||||
await initDbUpToMigration(MIGRATION_NAME);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const dbConnection = Container.get(DbConnection);
|
||||
await dbConnection.close();
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function to insert a test role with controlled timestamp
|
||||
*/
|
||||
async function insertTestRole(context: TestMigrationContext, roleData: RoleData): Promise<void> {
|
||||
const tableName = context.escape.tableName('role');
|
||||
const slugColumn = context.escape.columnName('slug');
|
||||
const displayNameColumn = context.escape.columnName('displayName');
|
||||
const createdAtColumn = context.escape.columnName('createdAt');
|
||||
const updatedAtColumn = context.escape.columnName('updatedAt');
|
||||
const systemRoleColumn = context.escape.columnName('systemRole');
|
||||
const roleTypeColumn = context.escape.columnName('roleType');
|
||||
const descriptionColumn = context.escape.columnName('description');
|
||||
|
||||
const systemRole = roleData.systemRole ?? false;
|
||||
const roleType = roleData.roleType ?? 'project';
|
||||
const description = roleData.description ?? null;
|
||||
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${createdAtColumn}, ${updatedAtColumn}, ${systemRoleColumn}, ${roleTypeColumn}, ${descriptionColumn}) VALUES (:slug, :displayName, :createdAt, :updatedAt, :systemRole, :roleType, :description)`,
|
||||
{
|
||||
slug: roleData.slug,
|
||||
displayName: roleData.displayName,
|
||||
createdAt: roleData.createdAt,
|
||||
updatedAt: roleData.createdAt,
|
||||
systemRole,
|
||||
roleType,
|
||||
description,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to retrieve all roles ordered by creation date
|
||||
*/
|
||||
async function getAllRoles(context: TestMigrationContext): Promise<RoleRow[]> {
|
||||
const tableName = context.escape.tableName('role');
|
||||
const slugColumn = context.escape.columnName('slug');
|
||||
const displayNameColumn = context.escape.columnName('displayName');
|
||||
const createdAtColumn = context.escape.columnName('createdAt');
|
||||
|
||||
const roles = await context.runQuery<RoleRow[]>(
|
||||
`SELECT ${slugColumn}, ${displayNameColumn}, ${createdAtColumn} FROM ${tableName} ORDER BY ${createdAtColumn} ASC`,
|
||||
{},
|
||||
);
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
describe('Schema Migration', () => {
|
||||
it('should create unique index and correctly rename all duplicate roles', async () => {
|
||||
// Create migration context for schema queries
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
|
||||
// Test Scenario 1: 3 roles with same displayName "Duplicate Name"
|
||||
await insertTestRole(context, {
|
||||
slug: 'test-role-oldest',
|
||||
displayName: 'Duplicate Name',
|
||||
createdAt: new Date('2024-01-01T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'test-role-middle',
|
||||
displayName: 'Duplicate Name',
|
||||
createdAt: new Date('2024-01-02T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'test-role-newest',
|
||||
displayName: 'Duplicate Name',
|
||||
createdAt: new Date('2024-01-03T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Test Scenario 2: 2 duplicate "Editor" roles
|
||||
await insertTestRole(context, {
|
||||
slug: 'editor-first',
|
||||
displayName: 'Editor',
|
||||
createdAt: new Date('2025-01-01T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'editor-second',
|
||||
displayName: 'Editor',
|
||||
createdAt: new Date('2025-01-02T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Test Scenario 3: 5 duplicate "Manager" roles
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await insertTestRole(context, {
|
||||
slug: `manager-${i}`,
|
||||
displayName: 'Manager',
|
||||
createdAt: new Date(`2025-02-0${i}T00:00:00.000Z`),
|
||||
});
|
||||
}
|
||||
|
||||
// Test Scenario 4: Multiple independent duplicate groups
|
||||
// Group 1: 3 "Admin" roles
|
||||
await insertTestRole(context, {
|
||||
slug: 'admin-1',
|
||||
displayName: 'Admin',
|
||||
createdAt: new Date('2025-03-01T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'admin-2',
|
||||
displayName: 'Admin',
|
||||
createdAt: new Date('2025-03-02T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'admin-3',
|
||||
displayName: 'Admin',
|
||||
createdAt: new Date('2025-03-03T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Group 2: 2 "Reviewer" roles
|
||||
await insertTestRole(context, {
|
||||
slug: 'reviewer-1',
|
||||
displayName: 'Reviewer',
|
||||
createdAt: new Date('2025-03-04T00:00:00.000Z'),
|
||||
});
|
||||
await insertTestRole(context, {
|
||||
slug: 'reviewer-2',
|
||||
displayName: 'Reviewer',
|
||||
createdAt: new Date('2025-03-05T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Check conflict with generated display name conflict
|
||||
await insertTestRole(context, {
|
||||
slug: 'reviewer-3',
|
||||
displayName: 'Reviewer 2',
|
||||
createdAt: new Date('2025-03-05T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Group 3: 1 "Viewer" role (no duplicates)
|
||||
await insertTestRole(context, {
|
||||
slug: 'viewer-1',
|
||||
displayName: 'Viewer',
|
||||
createdAt: new Date('2025-03-06T00:00:00.000Z'),
|
||||
});
|
||||
|
||||
// Verify pre-migration state - all roles exist with original displayNames
|
||||
const beforeRoles = await getAllRoles(context);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Duplicate Name')).toHaveLength(3);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Editor')).toHaveLength(2);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Manager')).toHaveLength(5);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Admin')).toHaveLength(3);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Reviewer')).toHaveLength(2);
|
||||
expect(beforeRoles.filter((r) => r.displayName === 'Viewer')).toHaveLength(1);
|
||||
|
||||
// Run the migration
|
||||
await runSingleMigration(MIGRATION_NAME);
|
||||
|
||||
// Release old query runner before creating new one
|
||||
await context.queryRunner.release();
|
||||
|
||||
// Create fresh context after migration
|
||||
const postMigrationContext = createTestMigrationContext(dataSource);
|
||||
|
||||
const tableName = postMigrationContext.escape.tableName('role');
|
||||
const displayNameColumn = postMigrationContext.escape.columnName('displayName');
|
||||
const slugColumn = postMigrationContext.escape.columnName('slug');
|
||||
const indexName = postMigrationContext.escape.indexName('UniqueRoleDisplayName');
|
||||
|
||||
// Verify all duplicate roles were renamed correctly
|
||||
const afterRoles = await getAllRoles(postMigrationContext);
|
||||
|
||||
// Test Scenario 1: 3 "Duplicate Name" roles
|
||||
const oldestRole = afterRoles.find((r) => r.slug === 'test-role-oldest');
|
||||
const middleRole = afterRoles.find((r) => r.slug === 'test-role-middle');
|
||||
const newestRole = afterRoles.find((r) => r.slug === 'test-role-newest');
|
||||
expect(oldestRole?.displayName).toBe('Duplicate Name'); // Oldest keeps original
|
||||
expect(middleRole?.displayName).toBe('Duplicate Name 2'); // Second gets " 2"
|
||||
expect(newestRole?.displayName).toBe('Duplicate Name 3'); // Third gets " 3"
|
||||
|
||||
// Test Scenario 2: 2 "Editor" roles
|
||||
const editorFirst = afterRoles.find((r) => r.slug === 'editor-first');
|
||||
const editorSecond = afterRoles.find((r) => r.slug === 'editor-second');
|
||||
expect(editorFirst?.displayName).toBe('Editor'); // Oldest keeps original
|
||||
expect(editorSecond?.displayName).toBe('Editor 2'); // Second gets " 2"
|
||||
|
||||
// Test Scenario 3: 5 "Manager" roles
|
||||
const manager1 = afterRoles.find((r) => r.slug === 'manager-1');
|
||||
const manager2 = afterRoles.find((r) => r.slug === 'manager-2');
|
||||
const manager3 = afterRoles.find((r) => r.slug === 'manager-3');
|
||||
const manager4 = afterRoles.find((r) => r.slug === 'manager-4');
|
||||
const manager5 = afterRoles.find((r) => r.slug === 'manager-5');
|
||||
expect(manager1?.displayName).toBe('Manager'); // Oldest keeps original
|
||||
expect(manager2?.displayName).toBe('Manager 2');
|
||||
expect(manager3?.displayName).toBe('Manager 3');
|
||||
expect(manager4?.displayName).toBe('Manager 4');
|
||||
expect(manager5?.displayName).toBe('Manager 5');
|
||||
|
||||
// Test Scenario 4: Multiple independent groups
|
||||
const admin1 = afterRoles.find((r) => r.slug === 'admin-1');
|
||||
const admin2 = afterRoles.find((r) => r.slug === 'admin-2');
|
||||
const admin3 = afterRoles.find((r) => r.slug === 'admin-3');
|
||||
expect(admin1?.displayName).toBe('Admin');
|
||||
expect(admin2?.displayName).toBe('Admin 2');
|
||||
expect(admin3?.displayName).toBe('Admin 3');
|
||||
|
||||
const reviewer1 = afterRoles.find((r) => r.slug === 'reviewer-1');
|
||||
const reviewer2 = afterRoles.find((r) => r.slug === 'reviewer-2');
|
||||
expect(reviewer1?.displayName).toBe('Reviewer');
|
||||
expect(reviewer2?.displayName).toBe('Reviewer 3');
|
||||
|
||||
const reviewer3 = afterRoles.find((r) => r.slug === 'reviewer-3');
|
||||
expect(reviewer3?.displayName).toBe('Reviewer 2');
|
||||
|
||||
const viewer1 = afterRoles.find((r) => r.slug === 'viewer-1');
|
||||
expect(viewer1?.displayName).toBe('Viewer'); // Unchanged (no duplicates)
|
||||
|
||||
// Verify unique index exists based on database type
|
||||
if (postMigrationContext.isSqlite) {
|
||||
const indexes = await postMigrationContext.queryRunner.query(
|
||||
`PRAGMA index_list(${tableName})`,
|
||||
);
|
||||
const uniqueIndex = indexes.find(
|
||||
(idx: { name: string; unique: number }) =>
|
||||
idx.name.includes('UniqueRoleDisplayName') && idx.unique === 1,
|
||||
);
|
||||
expect(uniqueIndex).toBeDefined();
|
||||
} else if (postMigrationContext.isPostgres) {
|
||||
// For PostgreSQL, we need the actual table/index names without quotes
|
||||
// The escaped indexName has quotes, so we need to strip them
|
||||
const actualTableName = postMigrationContext.tablePrefix + 'role';
|
||||
const actualIndexName = indexName.replace(/"/g, ''); // Remove quotes from escaped name
|
||||
const result = await postMigrationContext.runQuery(
|
||||
'SELECT indexname FROM pg_indexes WHERE tablename = :tableName AND indexname = :indexName',
|
||||
{ tableName: actualTableName, indexName: actualIndexName },
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
|
||||
// Verify index is unique
|
||||
const uniqueCheck = await postMigrationContext.runQuery<
|
||||
Array<{ index_name: string; indisunique: boolean }>
|
||||
>(
|
||||
`SELECT i.relname as index_name, ix.indisunique
|
||||
FROM pg_class t
|
||||
JOIN pg_index ix ON t.oid = ix.indrelid
|
||||
JOIN pg_class i ON i.oid = ix.indexrelid
|
||||
WHERE t.relname = :tableName AND i.relname = :indexName`,
|
||||
{ tableName: actualTableName, indexName: actualIndexName },
|
||||
);
|
||||
expect(uniqueCheck[0].indisunique).toBe(true);
|
||||
}
|
||||
|
||||
// Verify index enforces uniqueness by attempting duplicate insert
|
||||
await postMigrationContext.runQuery(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${postMigrationContext.escape.columnName('createdAt')}, ${postMigrationContext.escape.columnName('updatedAt')}, ${postMigrationContext.escape.columnName('systemRole')}, ${postMigrationContext.escape.columnName('roleType')}) VALUES (:slug, :displayName, :createdAt, :updatedAt, :systemRole, :roleType)`,
|
||||
{
|
||||
slug: 'test-duplicate-attempt',
|
||||
displayName: 'Unique Test Name',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
systemRole: false,
|
||||
roleType: 'project',
|
||||
},
|
||||
);
|
||||
|
||||
const attemptDuplicateInsert = async () => {
|
||||
return await postMigrationContext.queryRunner.query(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${postMigrationContext.escape.columnName('createdAt')}, ${postMigrationContext.escape.columnName('updatedAt')}, ${postMigrationContext.escape.columnName('systemRole')}, ${postMigrationContext.escape.columnName('roleType')}) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
'test-duplicate-attempt-2',
|
||||
'Unique Test Name',
|
||||
new Date(),
|
||||
new Date(),
|
||||
false,
|
||||
'project',
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
await expect(attemptDuplicateInsert()).rejects.toThrow();
|
||||
|
||||
// Cleanup
|
||||
await postMigrationContext.queryRunner.release();
|
||||
});
|
||||
|
||||
it('should remove unique index on rollback', async () => {
|
||||
// NOTE: This test skips duplicate scenarios since migration already ran in previous test
|
||||
// We're testing rollback functionality independently
|
||||
|
||||
// Run up() migration first (already done in test set up)
|
||||
// runSingleMigration checks if already executed and skips if needed
|
||||
await runSingleMigration(MIGRATION_NAME);
|
||||
|
||||
// Create fresh context
|
||||
const upContext = createTestMigrationContext(dataSource);
|
||||
|
||||
const tableName = upContext.escape.tableName('role');
|
||||
const indexName = upContext.escape.indexName('UniqueRoleDisplayName');
|
||||
|
||||
// Verify unique index exists
|
||||
if (upContext.isSqlite) {
|
||||
const indexes = await upContext.queryRunner.query(`PRAGMA index_list(${tableName})`);
|
||||
const uniqueIndex = indexes.find(
|
||||
(idx: { name: string; unique: number }) =>
|
||||
idx.name.includes('UniqueRoleDisplayName') && idx.unique === 1,
|
||||
);
|
||||
expect(uniqueIndex).toBeDefined();
|
||||
} else if (upContext.isPostgres) {
|
||||
// For PostgreSQL, we need the actual table/index names without quotes
|
||||
// The escaped indexName has quotes, so we need to strip them
|
||||
const actualTableName = upContext.tablePrefix + 'role';
|
||||
const actualIndexName = indexName.replace(/"/g, ''); // Remove quotes from escaped name
|
||||
const result = await upContext.runQuery(
|
||||
'SELECT indexname FROM pg_indexes WHERE tablename = :tableName AND indexname = :indexName',
|
||||
{ tableName: actualTableName, indexName: actualIndexName },
|
||||
);
|
||||
expect(result).toHaveLength(1);
|
||||
}
|
||||
|
||||
await upContext.queryRunner.release();
|
||||
|
||||
await undoLastSingleMigration();
|
||||
|
||||
// Create fresh context after rollback
|
||||
const postRollbackContext = createTestMigrationContext(dataSource);
|
||||
|
||||
// Verify index is removed (DB-specific queries)
|
||||
if (postRollbackContext.isSqlite) {
|
||||
const indexes = await postRollbackContext.runQuery<Array<{ name: string; unique: number }>>(
|
||||
`PRAGMA index_list(${tableName})`,
|
||||
);
|
||||
const uniqueIndex = indexes.find(
|
||||
(idx: { name: string; unique: number }) =>
|
||||
idx.name.includes('UniqueRoleDisplayName') && idx.unique === 1,
|
||||
);
|
||||
expect(uniqueIndex).toBeUndefined();
|
||||
} else if (postRollbackContext.isPostgres) {
|
||||
const result = await postRollbackContext.runQuery(
|
||||
'SELECT indexname FROM pg_indexes WHERE tablename = :tableName AND indexname = :indexName',
|
||||
{
|
||||
tableName: postRollbackContext.tablePrefix + 'role',
|
||||
indexName: indexName.replace(/"/g, ''),
|
||||
},
|
||||
);
|
||||
expect(result).toHaveLength(0);
|
||||
}
|
||||
|
||||
// Verify duplicate displayNames can be inserted again
|
||||
// Insert 2 roles with same displayName to confirm duplicates allowed
|
||||
const slugColumn = postRollbackContext.escape.columnName('slug');
|
||||
const displayNameColumn = postRollbackContext.escape.columnName('displayName');
|
||||
const createdAtColumn = postRollbackContext.escape.columnName('createdAt');
|
||||
const updatedAtColumn = postRollbackContext.escape.columnName('updatedAt');
|
||||
const systemRoleColumn = postRollbackContext.escape.columnName('systemRole');
|
||||
const roleTypeColumn = postRollbackContext.escape.columnName('roleType');
|
||||
|
||||
await postRollbackContext.runQuery(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${createdAtColumn}, ${updatedAtColumn}, ${systemRoleColumn}, ${roleTypeColumn}) VALUES (:slug, :displayName, :createdAt, :updatedAt, :systemRole, :roleType)`,
|
||||
{
|
||||
slug: 'rollback-test-1',
|
||||
displayName: 'Duplicate After Rollback',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
systemRole: false,
|
||||
roleType: 'project',
|
||||
},
|
||||
);
|
||||
|
||||
await postRollbackContext.runQuery(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${createdAtColumn}, ${updatedAtColumn}, ${systemRoleColumn}, ${roleTypeColumn}) VALUES (:slug, :displayName, :createdAt, :updatedAt, :systemRole, :roleType)`,
|
||||
{
|
||||
slug: 'rollback-test-2',
|
||||
displayName: 'Duplicate After Rollback',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
systemRole: false,
|
||||
roleType: 'project',
|
||||
},
|
||||
);
|
||||
|
||||
// Verify both roles were inserted successfully
|
||||
const duplicateRoles = await postRollbackContext.runQuery(
|
||||
`SELECT ${slugColumn} as slug, ${displayNameColumn} as displayName FROM ${tableName} WHERE ${displayNameColumn} = :displayName`,
|
||||
{ displayName: 'Duplicate After Rollback' },
|
||||
);
|
||||
|
||||
expect(duplicateRoles).toHaveLength(2);
|
||||
|
||||
// Cleanup
|
||||
await postRollbackContext.queryRunner.release();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post-Migration Capacity', () => {
|
||||
it('should accept unique displayNames after migration', async () => {
|
||||
const context = createTestMigrationContext(dataSource);
|
||||
|
||||
const tableName = context.escape.tableName('role');
|
||||
const slugColumn = context.escape.columnName('slug');
|
||||
const displayNameColumn = context.escape.columnName('displayName');
|
||||
const createdAtColumn = context.escape.columnName('createdAt');
|
||||
const updatedAtColumn = context.escape.columnName('updatedAt');
|
||||
const systemRoleColumn = context.escape.columnName('systemRole');
|
||||
const roleTypeColumn = context.escape.columnName('roleType');
|
||||
|
||||
// Insert role with unique displayName
|
||||
await context.runQuery(
|
||||
`INSERT INTO ${tableName} (${slugColumn}, ${displayNameColumn}, ${createdAtColumn}, ${updatedAtColumn}, ${systemRoleColumn}, ${roleTypeColumn}) VALUES (:slug, :displayName, :createdAt, :updatedAt, :systemRole, :roleType)`,
|
||||
{
|
||||
slug: 'unique-role-test',
|
||||
displayName: 'Unique Role Name',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
systemRole: false,
|
||||
roleType: 'project',
|
||||
},
|
||||
);
|
||||
|
||||
// Verify retrieval using SQL
|
||||
const results = await context.runQuery<Array<{ slug: string; displayName: string }>>(
|
||||
`SELECT ${slugColumn}, ${displayNameColumn} FROM ${tableName} WHERE ${slugColumn} = :slug`,
|
||||
{ slug: 'unique-role-test' },
|
||||
);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
// Access using the actual database column name
|
||||
const row = results[0] as Record<string, unknown>;
|
||||
expect(row.displayName ?? row.displayname).toBe('Unique Role Name');
|
||||
|
||||
// Cleanup
|
||||
await context.queryRunner.release();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user