Files
fleet/.github/scripts/build-fleetie-handles.test.js
Victor Lyuboslavsky d051cf082b Close stale fleetie-initiated issues. (#45530)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #45700 

Not a product change. This PR will allow us to run the workflow
manually. After ~2 weeks, if there are no issues, we'll make it
automatic.

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added automated workflow to identify and close stale issues created by
Fleet team members, with dry-run capability and operation limits.
* Added system to build and maintain a deduplicated list of Fleet team
member handles from GitHub organization and repository history.

* **Tests**
* Added comprehensive test suites for stale issue management and handle
list generation with mock GitHub API interactions and boundary condition
coverage.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/fleetdm/fleet/pull/45530?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-18 17:33:12 -05:00

74 lines
2.6 KiB
JavaScript

'use strict';
const test = require('node:test');
const assert = require('node:assert');
const { execFileSync } = require('node:child_process');
const path = require('node:path');
const SCRIPT = path.join(__dirname, 'build-fleetie-handles.js');
function runScript() {
// Force handbook-only mode so the test is hermetic (no network calls, no token required).
const env = { ...process.env, READ_ORG_TOKEN: '' };
delete env.FLEETIE_HANDLES_OUT;
const out = execFileSync('node', [SCRIPT], {
env,
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
});
return out.split('\n').filter(Boolean);
}
let handlesCache = null;
function getHandles() {
if (!handlesCache) handlesCache = runScript();
return handlesCache;
}
test('produces a sane number of handles', () => {
const handles = getHandles();
assert.ok(handles.length >= 50, `expected at least 50 handles, got ${handles.length}`);
});
test('output is sorted, lowercased, and deduplicated', () => {
const handles = getHandles();
const sorted = [...handles].sort();
assert.deepStrictEqual(handles, sorted, 'output is not sorted');
for (const handle of handles) {
assert.strictEqual(handle, handle.toLowerCase(), `handle not lowercased: ${handle}`);
}
assert.strictEqual(new Set(handles).size, handles.length, 'output contains duplicates');
});
test('includes known current Fleeties', () => {
const set = new Set(getHandles());
for (const handle of ['getvictor', 'lukeheath', 'mikermcneil', 'noahtalerman', 'eashaw']) {
assert.ok(set.has(handle), `expected current Fleetie ${handle} in handle list`);
}
});
test('includes known former Fleeties from git history', () => {
const set = new Set(getHandles());
// These handles have all appeared in handbook/company/product-groups.md at some point in git history
// but are not in the file at HEAD.
for (const handle of ['iansltx', 'mna', 'roperzh', 'ghernandez345']) {
assert.ok(set.has(handle), `expected former Fleetie ${handle} in handle list`);
}
});
test('excludes denylisted path segments and the org handle', () => {
const set = new Set(getHandles());
for (const denied of ['fleetdm', 'todo', 'user-attachments', 'orgs', 'issues', 'pull', 'apps']) {
assert.ok(!set.has(denied), `expected ${denied} to be filtered out`);
}
});
test('all handles match the GitHub username format', () => {
const handles = getHandles();
const githubHandleRe = /^[a-z0-9][a-z0-9-]{0,38}$/;
for (const handle of handles) {
assert.ok(githubHandleRe.test(handle), `invalid handle in output: ${handle}`);
assert.ok(!handle.endsWith('-'), `handle ends with hyphen: ${handle}`);
}
});