kt-template-online-api/test/admin/network-management/network-ddns-sql.spec.ts

185 lines
6.4 KiB
TypeScript

import { readFileSync } from 'fs';
import { resolve } from 'path';
const REPO_ROOT = resolve(__dirname, '../../..');
function readNormalizedSql(relativePath: string): string {
return readFileSync(resolve(REPO_ROOT, relativePath), 'utf8')
.replace(/`/g, '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function extractCreateTable(sql: string, tableName: string): string {
const match = sql.match(
new RegExp(
`create table if not exists ${tableName} \\(([\\s\\S]*?)\\) engine=innodb`,
),
);
expect(match).not.toBeNull();
return match?.[1] ?? '';
}
function expectDdnsTableContract(sql: string): void {
const table = extractCreateTable(sql, 'network_ddns_record');
const requiredColumns = [
'id bigint not null',
'name varchar(100) not null',
'remark text null',
'record_type varchar(8) not null',
'source_type varchar(32) not null',
'port_forward_id bigint null',
'domain varchar(253) not null',
'sub_domain varchar(253) not null',
'active_key varchar(300) null',
'enabled tinyint(1) not null default 0',
"sync_status varchar(32) not null default 'disabled'",
'provider_record_id varchar(32) null',
'source_address varchar(45) null',
'applied_address varchar(45) null',
'retry_count int unsigned not null default 0',
'next_retry_at datetime(3) null',
'last_attempt_at datetime(3) null',
'last_synced_at datetime(3) null',
'last_error_code varchar(64) null',
'last_error_message varchar(512) null',
'is_deleted tinyint(1) not null default 0',
'create_time datetime(3) not null default current_timestamp(3)',
'update_time datetime(3) not null default current_timestamp(3) on update current_timestamp(3)',
];
for (const column of requiredColumns) {
expect(table).toContain(column);
}
expect(table).toContain('primary key');
expect(table).toContain(
'unique key uk_network_ddns_record_active_key (active_key)',
);
expect(table).toContain(
'key idx_network_ddns_record_status (is_deleted, enabled, sync_status, next_retry_at)',
);
expect(table).toContain(
'key idx_network_ddns_record_port_forward (port_forward_id)',
);
}
describe('Network DDNS SQL contract', () => {
it('keeps the DDNS table aligned in incremental and refactor-v3 bootstrap schemas', () => {
expectDdnsTableContract(
readNormalizedSql('sql/network-management-init.sql'),
);
expectDdnsTableContract(
readNormalizedSql('sql/refactor-v3/00-full-schema.sql'),
);
});
it('adds idempotent Agent IPv6 columns and verifies the schema indexes', () => {
const incremental = readNormalizedSql('sql/network-management-init.sql');
const fullSchema = readNormalizedSql('sql/refactor-v3/00-full-schema.sql');
const verify = readNormalizedSql('sql/refactor-v3/99-verify.sql');
for (const sql of [incremental, fullSchema]) {
const agentState = extractCreateTable(sql, 'network_agent_state');
expect(agentState).toContain('current_public_ipv6 varchar(45) null');
expect(agentState).toContain('current_ipv6_observed_at datetime(3) null');
}
expect(incremental).toContain(
'alter table network_agent_state add column current_public_ipv6 varchar(45) null',
);
expect(incremental).toContain(
'alter table network_agent_state add column current_ipv6_observed_at datetime(3) null',
);
expect(incremental).toContain(
"table_name = 'network_agent_state' and column_name = 'current_public_ipv6'",
);
expect(incremental).toContain(
"table_name = 'network_agent_state' and column_name = 'current_ipv6_observed_at'",
);
expect(verify).toContain(
"select 'network_ddns_record' as table_name, count(*) as row_count from network_ddns_record",
);
expect(verify).toContain(
"column_name = 'current_public_ipv6' and column_type = 'varchar(45)'",
);
expect(verify).toContain(
"column_name = 'current_ipv6_observed_at' and column_type = 'datetime(3)'",
);
expect(verify).toContain(
"index_name = 'uk_network_ddns_record_active_key'",
);
expect(verify).toContain("index_name = 'idx_network_ddns_record_status'");
expect(verify).toContain(
"index_name = 'idx_network_ddns_record_port_forward'",
);
});
it('mirrors stable DDNS permissions and restricts grants to enabled super roles', () => {
const menu = readNormalizedSql('sql/network-management-menu.sql');
const seed = readNormalizedSql('sql/refactor-v3/01-seed-core.sql');
const vben = readNormalizedSql('sql/vben-admin-init.sql');
const incrementalNonSuperBlock = menu.match(
/where role\.role_code <> 'super' and menu\.name in \(([\s\S]*?)\);/,
)?.[1];
const incrementalSuperBlock = menu.match(
/join admin_menu menu on menu\.name in \(([\s\S]*?)\) where role\.role_code = 'super'/,
)?.[1];
const vbenNonSuperBlock = vben.match(
/and name not in \(([\s\S]*?)\);/,
)?.[1];
const permissions = [
{
code: 'system:network:ddns:list',
id: '2041700000000120222',
name: 'systemnetworkddnslist',
},
{
code: 'system:network:ddns:create',
id: '2041700000000120223',
name: 'systemnetworkddnscreate',
},
{
code: 'system:network:ddns:update',
id: '2041700000000120224',
name: 'systemnetworkddnsupdate',
},
{
code: 'system:network:ddns:delete',
id: '2041700000000120225',
name: 'systemnetworkddnsdelete',
},
{
code: 'system:network:ddns:retry',
id: '2041700000000120226',
name: 'systemnetworkddnsretry',
},
];
for (const sql of [menu, seed, vben]) {
for (const permission of permissions) {
expect(sql).toContain(permission.id);
expect(sql).toContain(permission.name);
expect(sql).toContain(permission.code);
}
}
expect(menu).toContain("where role.role_code <> 'super'");
expect(menu).toContain("where role.role_code = 'super'");
expect(menu).toContain('and role.status = 1');
expect(menu).toContain('and role.is_deleted = 0');
expect(incrementalNonSuperBlock).toBeDefined();
expect(incrementalSuperBlock).toBeDefined();
expect(vbenNonSuperBlock).toBeDefined();
for (const permission of permissions) {
expect(incrementalNonSuperBlock).toContain(permission.name);
expect(incrementalSuperBlock).toContain(permission.name);
expect(vbenNonSuperBlock).toContain(permission.name);
}
});
});