mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
test(doctor): minimal-PATH searchDirs regression and the non-admin gate
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
294ce0a667
commit
4152ee1015
@@ -3,7 +3,9 @@
|
|||||||
// `doctor --json`, prints a parseable DependencyReportJson on stdout, even when it exits
|
// `doctor --json`, prints a parseable DependencyReportJson on stdout, even when it exits
|
||||||
// non-zero because something required is missing.
|
// non-zero because something required is missing.
|
||||||
|
|
||||||
import { execFile } from 'node:child_process';
|
import { execFile, execFileSync } from 'node:child_process';
|
||||||
|
import { chmodSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
@@ -33,4 +35,43 @@ describe('codeman doctor --json', () => {
|
|||||||
expect(node?.status).toBe('ok');
|
expect(node?.status).toBe('ok');
|
||||||
expect(report.tools.every((t: { category: string }) => t.category === 'core')).toBe(true);
|
expect(report.tools.every((t: { category: string }) => t.category === 'core')).toBe(true);
|
||||||
}, 90_000);
|
}, 90_000);
|
||||||
|
|
||||||
|
// The report an operator got wrong in production: under systemd the PATH is minimal, so a CLI
|
||||||
|
// installed in ~/.local/bin read `missing` while the Run menu (which also searches the registry's
|
||||||
|
// searchDirs) found it. The PATH here holds nothing but `which`.
|
||||||
|
it('finds a CLI that lives only in a registry searchDirs entry when the PATH is minimal', async () => {
|
||||||
|
const home = mkdtempSync(join(tmpdir(), 'doctor-home-'));
|
||||||
|
const bare = mkdtempSync(join(tmpdir(), 'doctor-path-'));
|
||||||
|
try {
|
||||||
|
mkdirSync(join(home, '.local/bin'), { recursive: true });
|
||||||
|
const fake = join(home, '.local/bin/claude');
|
||||||
|
writeFileSync(fake, '#!/bin/sh\necho "2.1.0 (Claude Code)"\n');
|
||||||
|
chmodSync(fake, 0o755);
|
||||||
|
symlinkSync(execFileSyncWhich(), join(bare, 'which'));
|
||||||
|
const stdout = await new Promise<string>((resolve, reject) => {
|
||||||
|
execFile(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
join(ROOT, 'node_modules/tsx/dist/cli.mjs'),
|
||||||
|
join(ROOT, 'src/index.ts'),
|
||||||
|
'doctor',
|
||||||
|
'--json',
|
||||||
|
'--category',
|
||||||
|
'core',
|
||||||
|
],
|
||||||
|
{ timeout: 60_000, cwd: ROOT, env: { ...process.env, HOME: home, PATH: bare } },
|
||||||
|
(err, out) => (out ? resolve(out) : reject(err ?? new Error('no output')))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const claude = JSON.parse(stdout).tools.find((t: { id: string }) => t.id === 'claude');
|
||||||
|
expect(claude).toMatchObject({ status: 'ok', path: fake });
|
||||||
|
} finally {
|
||||||
|
rmSync(home, { recursive: true, force: true });
|
||||||
|
rmSync(bare, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
}, 90_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function execFileSyncWhich(): string {
|
||||||
|
return execFileSync('sh', ['-c', 'command -v which'], { encoding: 'utf-8' }).trim();
|
||||||
|
}
|
||||||
|
|||||||
@@ -93,4 +93,16 @@ describe('Diagnostics panel in a real browser', () => {
|
|||||||
await page.waitForFunction(() => /boom/.test(document.getElementById('doctorResult')?.textContent ?? ''));
|
await page.waitForFunction(() => /boom/.test(document.getElementById('doctorResult')?.textContent ?? ''));
|
||||||
expect(await page.isDisabled('#doctorRunBtn')).toBe(false);
|
expect(await page.isDisabled('#doctorRunBtn')).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('hides the Diagnostics group from a non-admin in multi-user mode and shows it to an admin', async () => {
|
||||||
|
const visible = (user: Record<string, unknown>) =>
|
||||||
|
page.evaluate((u) => {
|
||||||
|
(window as any).__codemanUser = u;
|
||||||
|
document.dispatchEvent(new CustomEvent('codeman:me'));
|
||||||
|
return getComputedStyle(document.getElementById('doctorGroup')!).display !== 'none';
|
||||||
|
}, user);
|
||||||
|
expect(await visible({ multiUser: true, role: 'user' })).toBe(false);
|
||||||
|
expect(await visible({ multiUser: true, role: 'admin' })).toBe(true);
|
||||||
|
expect(await visible({ multiUser: false })).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user