Files
harbor-net/test/deploy/runtime-impact.test.js
T
dokril 9055934e92
Build and Deploy Gateway / build-and-push (push) Successful in 25s
Build and Deploy Gateway / deploy (push) Successful in 13s
Fix failover channel layout and runtime impact coverage
2026-08-19 20:54:28 +03:00

176 lines
7.2 KiB
JavaScript

import assert from 'node:assert/strict';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { classifyRuntimeImpact } from '../../scripts/runtime-impact.mjs';
const root = path.resolve(import.meta.dirname, '../..');
function git(cwd, ...args) {
return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim();
}
test('runtime impact keeps affected components separate from restart scope', () => {
assert.deepEqual(classifyRuntimeImpact(['README.md', 'workpack/STATUS.md', 'test/example.test.js']), {
affectedComponents: [],
restartScope: 'none',
});
assert.deepEqual(classifyRuntimeImpact(['scripts/clean-test-dist.mjs']), {
affectedComponents: [],
restartScope: 'none',
});
assert.deepEqual(classifyRuntimeImpact(['src/web/App.tsx']), {
affectedComponents: ['control'],
restartScope: 'control',
});
assert.deepEqual(classifyRuntimeImpact(['src/server/services/domainTrafficService.ts']), {
affectedComponents: ['dataplane'],
restartScope: 'both',
});
assert.deepEqual(classifyRuntimeImpact(['package-lock.json']), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
});
});
test('every displaced regex path keeps dataplane restart coverage in JS and TS', () => {
const legacyPaths = [
'src/server/config',
'src/server/dataplane',
'src/server/gatewayRouting',
'src/server/singbox',
'src/server/singboxRuntime',
'src/server/version',
'src/server/adapters/neighbors',
'src/server/services/connectivityDiagnosticsService',
'src/server/services/deviceTrafficService',
'src/server/services/devicePolicyService',
'src/server/services/singboxSelectorService',
'src/shared/connectivityDiagnostics',
'src/shared/errors',
];
for (const file of legacyPaths) {
assert.equal(classifyRuntimeImpact([`${file}.js`]).restartScope, 'both', `${file}.js`);
assert.equal(classifyRuntimeImpact([`${file}.ts`]).restartScope, 'both', `${file}.ts`);
}
for (const file of ['Dockerfile', 'entrypoint.sh', 'package.json', 'package-lock.json',
'scripts/build-runtime-base.sh', '.gitea/workflows/gateway-build.yml']) {
assert.equal(classifyRuntimeImpact([file]).restartScope, 'both', file);
}
});
test('classifier covers current dataplane reachability without promoting its client adapter', () => {
for (const file of ['src/server/main.js', 'src/server/main.ts']) {
assert.deepEqual(classifyRuntimeImpact([file]), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
}, file);
}
assert.deepEqual(classifyRuntimeImpact(['src/server/services/domainTrafficService.js']), {
affectedComponents: ['dataplane'],
restartScope: 'both',
});
assert.deepEqual(classifyRuntimeImpact(['src/server/services/deviceInventoryService.js']), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
});
assert.deepEqual(classifyRuntimeImpact(['src/server/services/singboxSelectorService.ts']), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
});
assert.deepEqual(classifyRuntimeImpact(['src/server/dataplaneClient.ts']), {
affectedComponents: ['control'],
restartScope: 'control',
});
});
test('version bumps do not promote a control-only change to a dataplane restart', () => {
assert.deepEqual(classifyRuntimeImpact(['src/web/App.tsx', 'src/shared/versions.ts']), {
affectedComponents: ['control'],
restartScope: 'control',
});
});
test('release-critical inputs restart both and unknown paths fail closed', () => {
for (const file of [
'.dockerignore',
'.gitea/workflows/another.yml',
'docker-compose.gateway.yml',
'scripts/deploy-gateway.sh',
]) {
assert.deepEqual(classifyRuntimeImpact([file]), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
}, file);
}
assert.throws(() => classifyRuntimeImpact(['src/new-owner.ts']), /Unclassified path/);
assert.throws(() => classifyRuntimeImpact(['scripts/new-runtime.sh']), /Unclassified path/);
});
test('every tracked repository path has an explicit ownership class', () => {
const tracked = execFileSync('git', ['ls-files'], { cwd: root, encoding: 'utf8' })
.trim()
.split('\n');
assert.doesNotThrow(() => classifyRuntimeImpact(tracked));
});
test('workflow CLI reads changed paths from stdin', () => {
const result = spawnSync(
process.execPath,
['scripts/runtime-impact.mjs', '--stdin'],
{ cwd: root, input: 'src/web/App.tsx\nsrc/server/dataplane.ts\n', encoding: 'utf8' },
);
assert.equal(result.status, 0, result.stderr);
assert.equal(result.stdout.trim(), 'affected-components=control+dataplane\nrestart-scope=both');
});
test('push range keeps a dataplane change from an earlier commit', (t) => {
const repository = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-impact-'));
t.after(() => fs.rmSync(repository, { recursive: true, force: true }));
git(repository, 'init', '-q');
fs.writeFileSync(path.join(repository, 'README.md'), 'base\n');
git(repository, 'add', '.');
git(repository, '-c', 'user.name=Harbor', '-c', 'user.email=harbor@example.test', 'commit', '-qm', 'base');
const before = git(repository, 'rev-parse', 'HEAD');
const dataplane = path.join(repository, 'src/server/services/domainTrafficService.ts');
fs.mkdirSync(path.dirname(dataplane), { recursive: true });
fs.writeFileSync(dataplane, 'export {};\n');
git(repository, 'add', '.');
git(repository, '-c', 'user.name=Harbor', '-c', 'user.email=harbor@example.test', 'commit', '-qm', 'dataplane');
fs.writeFileSync(path.join(repository, 'README.md'), 'tip docs change\n');
git(repository, 'add', '.');
git(repository, '-c', 'user.name=Harbor', '-c', 'user.email=harbor@example.test', 'commit', '-qm', 'tip');
const changed = git(repository, 'diff', '--name-only', before, 'HEAD').split('\n');
assert.deepEqual(classifyRuntimeImpact(changed), {
affectedComponents: ['dataplane'],
restartScope: 'both',
});
});
test('rename range keeps ownership of the displaced dataplane path', (t) => {
const repository = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-impact-rename-'));
t.after(() => fs.rmSync(repository, { recursive: true, force: true }));
git(repository, 'init', '-q');
const oldPath = path.join(repository, 'src/server/services/domainTrafficService.ts');
fs.mkdirSync(path.dirname(oldPath), { recursive: true });
fs.writeFileSync(oldPath, 'export function dataplaneOwned() { return true; }\n');
git(repository, 'add', '.');
git(repository, '-c', 'user.name=Harbor', '-c', 'user.email=harbor@example.test', 'commit', '-qm', 'base');
const before = git(repository, 'rev-parse', 'HEAD');
fs.renameSync(oldPath, path.join(path.dirname(oldPath), 'newService.ts'));
git(repository, 'add', '-A');
git(repository, '-c', 'user.name=Harbor', '-c', 'user.email=harbor@example.test', 'commit', '-qm', 'rename');
const changed = git(repository, 'diff', '--no-renames', '--name-only', before, 'HEAD').split('\n');
assert.deepEqual(classifyRuntimeImpact(changed), {
affectedComponents: ['control', 'dataplane'],
restartScope: 'both',
});
});