Files
Codeman/test/custom-model-session-home-guard.test.ts
T
Codeman maintainer db5222c4fa fix: keep a home value that is not a path off disk, and re-apply from the own home (release review)
getCustomModelForPersist() trusted homeEnvVar to name a path, but the registry
types it as any env name, so a clis.json override could write a session's own
credential to state.json. Only an absolute path is written now.

A re-apply resolved the CLI's real home from what the session's map held, which
under a selection is the injected directory, and recovery resolved none at all.
An entry the own home lacks (settings.yaml) was then linked from the server's
home on an endpoint switch and on recovery. The route now reads the new
Session.ownEnvValue(), and recovery reads the clamped record through the same
bounded guard, under the bulk cap since it walks every saved session. Also
corrects the comment that said only a removed directory gets its links again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 02:39:11 +01:00

91 lines
3.9 KiB
TypeScript

/**
* @fileoverview `guardSessionConfigDirHome` probes a session-supplied configDir home before
* the apply runs synchronous fs calls on it (the #516 rule): a home that does not answer is
* hidden from the apply, so it links from the server's own home instead and says so.
*
* The probe is mocked: a mount that never answers is the case under test, and a real one
* cannot be made here.
*/
import { afterEach, describe, expect, it, vi } from 'vitest';
vi.mock('../src/utils/bounded-path-probe.js', async (importOriginal) => {
const actual = await importOriginal<typeof import('../src/utils/bounded-path-probe.js')>();
return { ...actual, probePathKind: vi.fn() };
});
import { getCli } from '../src/config/cli-registry/registry.js';
import { getDataDir } from '../src/config/instance.js';
import { writeCustomModelHosts } from '../src/custom-model-hosts.js';
import { customModelConfigDir, removeConfigDir } from '../src/custom-model-injection-apply.js';
import { Session } from '../src/session.js';
import { probePathKind } from '../src/utils/bounded-path-probe.js';
import { guardSessionConfigDirHome } from '../src/web/routes/session-routes.js';
import { WebServer } from '../src/web/server.js';
import type { CustomModelBookkeeping } from '../src/types/session.js';
const probe = vi.mocked(probePathKind);
const deepseek = () => getCli('deepseek')!;
const env =
(values: Record<string, string>) =>
(name: string): string | undefined =>
values[name];
describe('guardSessionConfigDirHome', () => {
afterEach(() => {
probe.mockReset();
vi.restoreAllMocks();
});
it('hides only the home when it does not answer, and warns that the server home is used', async () => {
probe.mockResolvedValue('unknown');
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const lookup = await guardSessionConfigDirHome(deepseek(), env({ DSH_HOME: '/mnt/dead/dsh', OTHER: 'kept' }));
expect(lookup('DSH_HOME')).toBeUndefined();
expect(lookup('OTHER')).toBe('kept');
expect(probe).toHaveBeenCalledWith('/mnt/dead/dsh', { pastCap: true });
expect(warn).toHaveBeenCalledTimes(1);
expect(String(warn.mock.calls[0][0])).toContain('/mnt/dead/dsh');
});
it('passes a home that answers straight through', async () => {
probe.mockResolvedValue('directory');
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const base = env({ DSH_HOME: '/home/u/.dsh' });
expect(await guardSessionConfigDirHome(deepseek(), base)).toBe(base);
expect(warn).not.toHaveBeenCalled();
});
it('boot recovery probes the own home under the stall cap: it is a bulk pass, not one request', async () => {
probe.mockResolvedValue('unknown');
vi.spyOn(console, 'warn').mockImplementation(() => {});
await writeCustomModelHosts(getDataDir(), [{ id: 'ep', label: 'Ep', baseUrl: 'http://box:8080' }]);
const session = new Session({ workingDir: '/tmp', mode: 'deepseek' });
const saved: CustomModelBookkeeping = { endpointId: 'ep', modelId: 'q', envKeys: [] };
const proto = WebServer.prototype as unknown as {
_rebuildCustomModelEnv: (session: Session, saved: CustomModelBookkeeping, ownEnv: object) => Promise<unknown>;
};
await proto._rebuildCustomModelEnv(session, saved, { DSH_HOME: '/mnt/dead/dsh' });
expect(probe).toHaveBeenCalledWith('/mnt/dead/dsh', {});
removeConfigDir(customModelConfigDir(session.id));
});
it('never probes a relative home: the apply refuses to join one', async () => {
const base = env({ DSH_HOME: 'relative/dsh' });
expect(await guardSessionConfigDirHome(deepseek(), base)).toBe(base);
expect(probe).not.toHaveBeenCalled();
});
it('never probes for a CLI with no linkFromRealHome recipe', async () => {
const base = env({ CODEX_HOME: '/mnt/dead/codex' });
expect(await guardSessionConfigDirHome(getCli('codex')!, base)).toBe(base);
expect(probe).not.toHaveBeenCalled();
});
});