mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-28 15:27:03 +02:00
174 lines
4.9 KiB
JavaScript
174 lines
4.9 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
assignOwnership,
|
|
ownershipsToAllocations,
|
|
parseOwnersContent,
|
|
parseOwnersFile,
|
|
} from './owners.mjs';
|
|
|
|
/**
|
|
* Run these tests by running
|
|
*
|
|
* node --test ./.github/scripts/owners.test.mjs
|
|
* */
|
|
|
|
describe('parseOwnersContent', () => {
|
|
it('parses well-formed OWNERS lines into Owner records', () => {
|
|
const content = [
|
|
'# header comment',
|
|
'',
|
|
'* @n8n-io/catalysts',
|
|
'packages/cli/ @n8n-io/cli-team',
|
|
'packages/foo/bar.ts @n8n-io/some-team',
|
|
].join('\n');
|
|
|
|
assert.deepEqual(parseOwnersContent(content), [
|
|
{ filepath: '*', team: '@n8n-io/catalysts' },
|
|
{ filepath: 'packages/cli/', team: '@n8n-io/cli-team' },
|
|
{ filepath: 'packages/foo/bar.ts', team: '@n8n-io/some-team' },
|
|
]);
|
|
});
|
|
|
|
it('skips lines without an @n8n-io team', () => {
|
|
const content = [
|
|
'# comment',
|
|
'* @other-org/team',
|
|
'pkg/ @n8n-io/keepers',
|
|
'',
|
|
].join('\n');
|
|
|
|
assert.deepEqual(parseOwnersContent(content), [
|
|
{ filepath: 'pkg/', team: '@n8n-io/keepers' },
|
|
]);
|
|
});
|
|
|
|
it('returns an empty array when no lines reference @n8n-io', () => {
|
|
assert.deepEqual(parseOwnersContent('# nothing here\n* @someone-else'), []);
|
|
});
|
|
});
|
|
|
|
describe('parseOwnersFile', () => {
|
|
it('reads the real OWNERS file into well-formed Owner records', () => {
|
|
const owners = parseOwnersFile();
|
|
|
|
assert.ok(owners.length > 0, 'OWNERS file should not be empty');
|
|
assert.ok(
|
|
owners.every(o => o.team && o.filepath),
|
|
'every parsed entry should have both team and filepath',
|
|
);
|
|
assert.ok(
|
|
owners.every(o => o.team?.startsWith('@n8n-io/')),
|
|
'every parsed team should belong to the @n8n-io org',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('assignOwnership', () => {
|
|
it('assigns every file to the catch-all team when only `*` is defined', () => {
|
|
const files = new Set(['a.ts', 'packages/cli/src/index.ts', 'docs/readme.md']);
|
|
const owners = [{ filepath: '*', team: '@n8n-io/catalysts' }];
|
|
|
|
const result = assignOwnership(files, owners);
|
|
|
|
assert.deepEqual(
|
|
result.get('@n8n-io/catalysts')?.sort(),
|
|
[...files].sort(),
|
|
);
|
|
assert.equal(result.size, 1);
|
|
});
|
|
|
|
it('applies last-match-wins: a later specific rule overrides the catch-all', () => {
|
|
const files = new Set([
|
|
'README.md',
|
|
'packages/cli/src/index.ts',
|
|
'packages/cli/src/lib/foo.ts',
|
|
]);
|
|
const owners = [
|
|
{ filepath: '*', team: '@n8n-io/catalysts' },
|
|
{ filepath: 'packages/cli/', team: '@n8n-io/cli-team' },
|
|
];
|
|
|
|
const result = assignOwnership(files, owners);
|
|
|
|
assert.deepEqual(result.get('@n8n-io/catalysts'), ['README.md']);
|
|
assert.deepEqual(
|
|
result.get('@n8n-io/cli-team')?.sort(),
|
|
['packages/cli/src/index.ts', 'packages/cli/src/lib/foo.ts'].sort(),
|
|
);
|
|
});
|
|
|
|
it('matches a directory pattern recursively', () => {
|
|
const files = new Set([
|
|
'packages/cli/src/deep/nested/file.ts',
|
|
'packages/cli/package.json',
|
|
]);
|
|
const owners = [{ filepath: 'packages/cli/', team: '@n8n-io/cli-team' }];
|
|
|
|
const result = assignOwnership(files, owners);
|
|
|
|
assert.deepEqual(
|
|
result.get('@n8n-io/cli-team')?.sort(),
|
|
[...files].sort(),
|
|
);
|
|
});
|
|
|
|
it('matches an exact file pattern only against that file', () => {
|
|
const files = new Set([
|
|
'packages/cli/src/controllers/ai.controller.ts',
|
|
'packages/cli/src/controllers/other.controller.ts',
|
|
]);
|
|
const owners = [
|
|
{
|
|
filepath: 'packages/cli/src/controllers/ai.controller.ts',
|
|
team: '@n8n-io/ai-team',
|
|
},
|
|
];
|
|
|
|
const result = assignOwnership(files, owners);
|
|
|
|
assert.deepEqual(result.get('@n8n-io/ai-team'), [
|
|
'packages/cli/src/controllers/ai.controller.ts',
|
|
]);
|
|
// the other controller matched no rule, so it must be omitted entirely
|
|
assert.equal(result.size, 1);
|
|
});
|
|
|
|
it('omits files that match no rule (no catch-all present)', () => {
|
|
const files = new Set(['unowned/file.ts', 'packages/cli/src/x.ts']);
|
|
const owners = [{ filepath: 'packages/cli/', team: '@n8n-io/cli-team' }];
|
|
|
|
const result = assignOwnership(files, owners);
|
|
|
|
assert.deepEqual(result.get('@n8n-io/cli-team'), ['packages/cli/src/x.ts']);
|
|
assert.equal(result.size, 1);
|
|
});
|
|
|
|
it('returns an empty Map when there are no changed files', () => {
|
|
const owners = [{ filepath: '*', team: '@n8n-io/catalysts' }];
|
|
const result = assignOwnership(new Set(), owners);
|
|
|
|
assert.equal(result.size, 0);
|
|
});
|
|
});
|
|
|
|
describe('ownershipsToAllocations', () => {
|
|
it('converts a Map of team -> files into Allocation[] with fileCount', () => {
|
|
const ownerships = new Map([
|
|
['@n8n-io/cli-team', ['a.ts', 'b.ts', 'c.ts']],
|
|
['@n8n-io/catalysts', ['README.md']],
|
|
]);
|
|
|
|
const result = ownershipsToAllocations(ownerships);
|
|
|
|
assert.deepEqual(result, [
|
|
{ team: '@n8n-io/cli-team', fileCount: 3 },
|
|
{ team: '@n8n-io/catalysts', fileCount: 1 },
|
|
]);
|
|
});
|
|
|
|
it('returns an empty array for an empty Map', () => {
|
|
assert.deepEqual(ownershipsToAllocations(new Map()), []);
|
|
});
|
|
});
|