feat(GOV-009): 支持固定开发库结构基线
This commit is contained in:
@@ -11,11 +11,13 @@ import {
|
||||
assertCurrentReleasedBaselinePublished,
|
||||
assertSafeDevelopmentConfiguration,
|
||||
buildCapturedArtifacts,
|
||||
buildCapturedEmptyArtifacts,
|
||||
buildMysqldumpArguments,
|
||||
buildNotApplicableArtifacts,
|
||||
normalizeSchemaSql,
|
||||
persistBaselineArtifacts,
|
||||
runBaselineGeneration,
|
||||
resolveFixedDevelopmentEnv,
|
||||
assertRemoteBranchMatches,
|
||||
verifyBaselineDirectory,
|
||||
} from './schema-baseline.mjs';
|
||||
@@ -76,10 +78,37 @@ test('拒绝任何非开发环境和非固定开发库配置', () => {
|
||||
...validConfig,
|
||||
MYSQL_HOST: 'forbidden-remote.example.invalid',
|
||||
}),
|
||||
/开发机回环地址/,
|
||||
/固定开发数据库主机白名单/,
|
||||
);
|
||||
});
|
||||
|
||||
test('固定远程开发库白名单只接受指定地址', () => {
|
||||
assert.doesNotThrow(() =>
|
||||
assertSafeDevelopmentConfiguration({
|
||||
...validConfig,
|
||||
MYSQL_HOST: '112.192.20.11',
|
||||
}),
|
||||
);
|
||||
for (const host of ['112.192.20.12', 'dev-db.example.invalid', '']) {
|
||||
assert.throws(
|
||||
() => assertSafeDevelopmentConfiguration({ ...validConfig, MYSQL_HOST: host }),
|
||||
/固定开发数据库主机白名单/,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('本地 Schema 配置优先于无秘密模板', () => {
|
||||
const root = makeTemporaryDirectory();
|
||||
const envDirectory = path.join(root, 'deploy', 'env', 'development');
|
||||
mkdirSync(envDirectory, { recursive: true });
|
||||
const localEnv = path.join(envDirectory, 'api.env.local');
|
||||
const templateEnv = path.join(envDirectory, 'api.env.example');
|
||||
writeFileSync(localEnv, 'APP_ENV=development\n');
|
||||
writeFileSync(templateEnv, 'APP_ENV=template\n');
|
||||
|
||||
assert.equal(resolveFixedDevelopmentEnv(root), localEnv);
|
||||
});
|
||||
|
||||
test('拒绝错误字符集、排序规则和不完整的启用配置', () => {
|
||||
assert.throws(
|
||||
() => assertSafeDevelopmentConfiguration({ ...validConfig, MYSQL_CHARSET: 'utf8' }),
|
||||
@@ -357,7 +386,7 @@ test('幂等重跑仍核对清单提交与发布标签提交', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('校验能发现 SQL 被修改且生成阶段拒绝空基线', () => {
|
||||
test('校验能发现 SQL 被修改且空结构转为 captured_empty', () => {
|
||||
const root = makeTemporaryDirectory();
|
||||
const target = path.join(root, 'v0.0.5');
|
||||
persistBaselineArtifacts(
|
||||
@@ -367,9 +396,67 @@ test('校验能发现 SQL 被修改且生成阶段拒绝空基线', () => {
|
||||
writeFileSync(path.join(target, 'schema.sql'), 'CREATE TABLE `tampered` (`id` int);\n');
|
||||
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /校验值不一致/);
|
||||
|
||||
assert.throws(
|
||||
() => buildCapturedArtifacts({ ...validMetadata, applicationVersion: 'v0.0.4' }, ''),
|
||||
/不能为空/,
|
||||
assert.equal(
|
||||
buildCapturedArtifacts({ ...validMetadata, applicationVersion: 'v0.0.4' }, '').status,
|
||||
'captured_empty',
|
||||
);
|
||||
});
|
||||
|
||||
test('启用的空开发库生成 captured_empty format v2 清单', () => {
|
||||
const artifacts = buildCapturedEmptyArtifacts({
|
||||
...validMetadata,
|
||||
applicationVersion: 'v0.0.8',
|
||||
});
|
||||
const manifest = JSON.parse(artifacts.files['manifest.yaml']);
|
||||
|
||||
assert.equal(artifacts.status, 'captured_empty');
|
||||
assert.deepEqual(Object.keys(artifacts.files).sort(), ['manifest.yaml']);
|
||||
assert.equal(manifest.format_version, 2);
|
||||
assert.equal(manifest.status, 'captured_empty');
|
||||
assert.equal(manifest.schema_sha256, createHash('sha256').update('').digest('hex'));
|
||||
assert.deepEqual(manifest.object_counts, {
|
||||
tables: 0,
|
||||
views: 0,
|
||||
triggers: 0,
|
||||
routines: 0,
|
||||
events: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test('启用入口把 mysqldump 空结构持久化为 captured_empty', () => {
|
||||
const root = makeTemporaryDirectory();
|
||||
const envFile = path.join(root, 'development.env');
|
||||
writeFileSync(
|
||||
envFile,
|
||||
Object.entries(validConfig)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n'),
|
||||
);
|
||||
|
||||
const result = runBaselineGeneration({
|
||||
repositoryRoot: root,
|
||||
envFile,
|
||||
applicationVersion: 'v0.0.8',
|
||||
gitCommit: validMetadata.gitCommit,
|
||||
generatedAt: validMetadata.generatedAt,
|
||||
executeFile(command, args) {
|
||||
if (args[0] === '--version') {
|
||||
return `${command} Ver 14.14 Distrib 5.7.44, for Linux`;
|
||||
}
|
||||
if (command === 'mysql') {
|
||||
return '5.7.44-log\tutf8mb4\tutf8mb4_general_ci\n';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(result.status, 'captured_empty');
|
||||
assert.deepEqual(
|
||||
readFileSync(
|
||||
path.join(root, 'database', 'schema-baselines', 'v0.0.8', 'manifest.yaml'),
|
||||
'utf8',
|
||||
).includes('captured_empty'),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -431,7 +518,7 @@ test('不适用清单拒绝未知对象键和伪造的格式元数据', () => {
|
||||
assert.throws(() => verifyBaselineDirectory(target, 'v0.0.5'), /清单格式/);
|
||||
});
|
||||
|
||||
test('不适用基线只在当前开发环境仍关闭 MySQL 时有效', () => {
|
||||
test('历史不适用基线不被当前开发配置反向否定', () => {
|
||||
const root = makeTemporaryDirectory();
|
||||
const envDirectory = path.join(root, 'deploy', 'env', 'development');
|
||||
const envFile = path.join(envDirectory, 'api.env.example');
|
||||
@@ -452,9 +539,8 @@ test('不适用基线只在当前开发环境仍关闭 MySQL 时有效', () => {
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n'),
|
||||
);
|
||||
assert.throws(
|
||||
assert.doesNotThrow(
|
||||
() => assertNotApplicableMatchesDevelopmentConfiguration(root, result),
|
||||
/已启用 MySQL.*not_applicable/,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user