Files
MyGoNavi/frontend/src/utils/rowLocator.test.ts
Syngnat b1ef52f62e feat(data-grid): 支持无主键表安全编辑
- 定位策略:新增主键、唯一索引和 Oracle ROWID 三类安全行定位能力
- 查询编辑器:简单单表 SELECT 自动补充隐藏定位列,复杂结果保持只读
- 表预览:无主键表可通过唯一索引或 Oracle ROWID 安全编辑
- 提交流程:移除无主键整行 WHERE fallback,隐藏定位列不参与展示和写入
- 后端保护:Oracle、MySQL、PostgreSQL 更新删除必须恰好影响 1 行
- 测试覆盖:补充 QueryEditor、DataViewer、DataGrid 和 ApplyChanges 相关用例
Refs #419
2026-04-29 12:33:35 +08:00

147 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from 'vitest';
import {
ORACLE_ROWID_LOCATOR_COLUMN,
filterHiddenLocatorColumns,
resolveEditRowLocator,
resolveRowLocatorValues,
} from './rowLocator';
const uniqueIndex = (name: string, columnName: string, seqInIndex = 1) => ({
name,
columnName,
seqInIndex,
nonUnique: 0,
indexType: 'BTREE',
});
const normalIndex = (name: string, columnName: string, seqInIndex = 1) => ({
name,
columnName,
seqInIndex,
nonUnique: 1,
indexType: 'BTREE',
});
describe('resolveEditRowLocator', () => {
it('prefers primary keys over unique indexes', () => {
expect(resolveEditRowLocator({
dbType: 'mysql',
resultColumns: ['ID', 'EMAIL'],
primaryKeys: ['ID'],
indexes: [uniqueIndex('uk_email', 'EMAIL')],
})).toEqual({
strategy: 'primary-key',
columns: ['ID'],
valueColumns: ['ID'],
readOnly: false,
});
});
it('uses a unique index when there is no primary key', () => {
expect(resolveEditRowLocator({
dbType: 'mysql',
resultColumns: ['EMAIL', 'NAME'],
indexes: [uniqueIndex('uk_email', 'EMAIL')],
})).toEqual({
strategy: 'unique-key',
columns: ['EMAIL'],
valueColumns: ['EMAIL'],
readOnly: false,
});
});
it('sorts composite unique index columns by sequence', () => {
expect(resolveEditRowLocator({
dbType: 'postgres',
resultColumns: ['TENANT_ID', 'CODE', 'NAME'],
indexes: [
uniqueIndex('uk_tenant_code', 'CODE', 2),
uniqueIndex('uk_tenant_code', 'TENANT_ID', 1),
],
})).toMatchObject({
strategy: 'unique-key',
columns: ['TENANT_ID', 'CODE'],
valueColumns: ['TENANT_ID', 'CODE'],
readOnly: false,
});
});
it('ignores non-unique indexes', () => {
expect(resolveEditRowLocator({
dbType: 'mysql',
resultColumns: ['NAME'],
indexes: [normalIndex('idx_name', 'NAME')],
})).toMatchObject({
strategy: 'none',
readOnly: true,
});
});
it('keeps results read-only when primary key columns are missing from result columns', () => {
expect(resolveEditRowLocator({
dbType: 'oracle',
resultColumns: ['NAME'],
primaryKeys: ['ID'],
})).toMatchObject({
strategy: 'none',
readOnly: true,
reason: '结果集中缺少主键列 ID无法安全提交修改。',
});
});
it('uses Oracle ROWID when no primary or unique key is available', () => {
expect(resolveEditRowLocator({
dbType: 'oracle',
resultColumns: ['NAME', ORACLE_ROWID_LOCATOR_COLUMN],
allowOracleRowID: true,
})).toEqual({
strategy: 'oracle-rowid',
columns: ['ROWID'],
valueColumns: [ORACLE_ROWID_LOCATOR_COLUMN],
hiddenColumns: [ORACLE_ROWID_LOCATOR_COLUMN],
readOnly: false,
});
});
});
describe('resolveRowLocatorValues', () => {
it('extracts locator values from the original row', () => {
const locator = resolveEditRowLocator({
dbType: 'mysql',
resultColumns: ['EMAIL', 'NAME'],
indexes: [uniqueIndex('uk_email', 'EMAIL')],
});
expect(resolveRowLocatorValues(locator, { EMAIL: 'a@example.com', NAME: 'A' })).toEqual({
ok: true,
values: { EMAIL: 'a@example.com' },
});
});
it('rejects nullable unique locator values', () => {
const locator = resolveEditRowLocator({
dbType: 'mysql',
resultColumns: ['EMAIL', 'NAME'],
indexes: [uniqueIndex('uk_email', 'EMAIL')],
});
expect(resolveRowLocatorValues(locator, { EMAIL: null, NAME: 'A' })).toEqual({
ok: false,
error: '定位列 EMAIL 的值为空,无法安全提交修改。',
});
});
});
describe('filterHiddenLocatorColumns', () => {
it('removes hidden Oracle ROWID columns from displayed columns', () => {
const locator = resolveEditRowLocator({
dbType: 'oracle',
resultColumns: ['NAME', ORACLE_ROWID_LOCATOR_COLUMN],
allowOracleRowID: true,
});
expect(filterHiddenLocatorColumns(['NAME', ORACLE_ROWID_LOCATOR_COLUMN], locator)).toEqual(['NAME']);
});
});