Publish curated ForgeFlow source from 2ed1787c0b52
This commit is contained in:
commit
f60b269686
254 files changed
+46204
No files matched your search
@@ -0,0 +1,152 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import monitorModule from '../src/main/repository-monitor.cjs';
|
||||
|
||||
const { RepositoryMonitor } = monitorModule;
|
||||
|
||||
test('repository monitor establishes a baseline and emits only on later changes', async () => {
|
||||
let revision = 1;
|
||||
const changes = [];
|
||||
const git = {
|
||||
status: async (localPath) => ({ localPath, revision }),
|
||||
statusFingerprint: (status) => String(status.revision)
|
||||
};
|
||||
const store = { data: { preferences: { autoRefresh: true, repositoryPollSeconds: 2 } } };
|
||||
const monitor = new RepositoryMonitor({ store, git, onChange: (change) => changes.push(change) });
|
||||
monitor.setPaths(['/repo']);
|
||||
await monitor.tick();
|
||||
assert.equal(changes.length, 0);
|
||||
revision = 2;
|
||||
await monitor.tick();
|
||||
assert.equal(changes.length, 1);
|
||||
assert.equal(changes[0].reason, 'working-tree-changed');
|
||||
monitor.pause('/repo');
|
||||
revision = 3;
|
||||
await monitor.tick();
|
||||
assert.equal(changes.length, 1);
|
||||
monitor.resume('/repo');
|
||||
await monitor.tick();
|
||||
assert.equal(changes.length, 2);
|
||||
});
|
||||
test('repository monitor checks multiple repositories concurrently with a bounded worker pool', async () => {
|
||||
let active = 0;
|
||||
let peak = 0;
|
||||
const git = {
|
||||
status: async (localPath) => {
|
||||
active += 1;
|
||||
peak = Math.max(peak, active);
|
||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
||||
active -= 1;
|
||||
return { localPath, revision: 1 };
|
||||
},
|
||||
statusFingerprint: (status) => String(status.revision)
|
||||
};
|
||||
const store = { data: { preferences: { autoRefresh: true, repositoryPollSeconds: 2 } } };
|
||||
const monitor = new RepositoryMonitor({ store, git });
|
||||
monitor.setPaths(Array.from({ length: 10 }, (_, index) => `/repo-${index}`));
|
||||
await monitor.tick();
|
||||
assert.equal(peak, 4);
|
||||
assert.equal(active, 0);
|
||||
assert.equal(monitor.fingerprints.size, 10);
|
||||
});
|
||||
|
||||
test('a watched repository is read on filesystem activity instead of on every interval', async (context) => {
|
||||
const { mkdtemp, mkdir, writeFile, rm } = await import('node:fs/promises');
|
||||
const os = await import('node:os');
|
||||
const path = await import('node:path');
|
||||
const root = await mkdtemp(path.join(os.tmpdir(), 'forgeflow-watch-'));
|
||||
context.after(() => rm(root, { recursive: true, force: true }));
|
||||
await mkdir(path.join(root, '.git'), { recursive: true });
|
||||
|
||||
let revision = 1;
|
||||
const reads = [];
|
||||
const changes = [];
|
||||
const git = {
|
||||
status: async (localPath) => { reads.push(localPath); return { localPath, revision }; },
|
||||
statusFingerprint: (status) => String(status.revision)
|
||||
};
|
||||
const store = { data: { preferences: { autoRefresh: true, repositoryPollSeconds: 2 } } };
|
||||
const monitor = new RepositoryMonitor({ store, git, onChange: (change) => changes.push(change) });
|
||||
context.after(() => monitor.stop());
|
||||
|
||||
monitor.restart();
|
||||
monitor.setPaths([root]);
|
||||
if (!monitor.watchers.has(root)) {
|
||||
context.skip('this platform does not support recursive directory watching');
|
||||
return;
|
||||
}
|
||||
|
||||
await monitor.tick();
|
||||
assert.equal(reads.length, 1, 'the baseline is established once');
|
||||
|
||||
// Without filesystem activity the interval must not spawn another read.
|
||||
await monitor.tick();
|
||||
assert.equal(reads.length, 1);
|
||||
|
||||
revision = 2;
|
||||
await writeFile(path.join(root, 'feature.txt'), 'changed\n');
|
||||
// Exercise the monitor's filesystem-activity boundary deterministically.
|
||||
// Native fs.watch delivery is platform/overlay specific and is covered by
|
||||
// the product's safety interval rather than by this unit test.
|
||||
monitor.noteFilesystemChange(root);
|
||||
// The watcher debounce and the per-repository cooldown both apply here.
|
||||
const deadline = Date.now() + 5_000;
|
||||
while (changes.length === 0 && Date.now() < deadline) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
assert.ok(reads.length > 1, 'filesystem activity triggers a read');
|
||||
assert.equal(changes.length, 1);
|
||||
assert.equal(changes[0].reason, 'working-tree-changed');
|
||||
|
||||
const readsAfterChange = reads.length;
|
||||
await new Promise((resolve) => setTimeout(resolve, 800));
|
||||
assert.equal(reads.length, readsAfterChange, 'a quiet repository is not read again');
|
||||
|
||||
monitor.stop();
|
||||
assert.equal(monitor.watchers.size, 0, 'stopping releases every watcher');
|
||||
});
|
||||
|
||||
test('background Gitea awareness fetches read-only remote state with bounded concurrency', async () => {
|
||||
let active = 0;
|
||||
let peak = 0;
|
||||
const changes = [];
|
||||
const git = {
|
||||
fetch: async (localPath) => {
|
||||
active += 1;
|
||||
peak = Math.max(peak, active);
|
||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
||||
active -= 1;
|
||||
return { status: { localPath, revision: 2, branch: { head: 'main', ahead: 0, behind: 1 }, counts: {} } };
|
||||
},
|
||||
statusFingerprint: (status) => String(status.revision),
|
||||
};
|
||||
const store = { data: { preferences: { autoRefresh: true, repositoryPollSeconds: 2, fetchIntervalMinutes: 1 } } };
|
||||
const monitor = new RepositoryMonitor({ store, git, onChange: (change) => changes.push(change) });
|
||||
const paths = Array.from({ length: 6 }, (_, index) => `/repo-${index}`);
|
||||
monitor.setPaths(paths);
|
||||
for (const localPath of paths) {
|
||||
monitor.fingerprints.set(localPath, '1');
|
||||
monitor.lastFetchedAt.set(localPath, Date.now() - 61_000);
|
||||
}
|
||||
|
||||
await monitor.fetchRemoteUpdates();
|
||||
assert.equal(peak, 2);
|
||||
assert.equal(active, 0);
|
||||
assert.equal(changes.length, paths.length);
|
||||
assert.ok(changes.every((change) => change.reason === 'remote-state-changed'));
|
||||
});
|
||||
|
||||
test('a zero remote fetch interval disables background network access', async () => {
|
||||
let fetches = 0;
|
||||
const git = {
|
||||
fetch: async () => { fetches += 1; return { status: { revision: 2 } }; },
|
||||
statusFingerprint: (status) => String(status.revision),
|
||||
};
|
||||
const store = { data: { preferences: { autoRefresh: true, repositoryPollSeconds: 2, fetchIntervalMinutes: 0 } } };
|
||||
const monitor = new RepositoryMonitor({ store, git });
|
||||
monitor.setPaths(['/repo']);
|
||||
monitor.lastFetchedAt.set('/repo', 0);
|
||||
await monitor.fetchRemoteUpdates(Date.now() + 24 * 60 * 60_000);
|
||||
assert.equal(fetches, 0);
|
||||
});
|
||||
Reference in new issue
Block a user