Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions src/config-writer-new-branches.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* Branch and line coverage for previously-uncovered paths in src/config-writer.ts:
* - validateAndPrepareWorkDir: workDir is not a directory (line 58)
* - copySeccompProfile: alt-path seccomp fallback (lines 95-97), neither path found
* - writeAuditArtifacts: auditDir is not a directory (line 162)
*
* This file sets up its own complete fs mock to gain control over statSync and
* copyFileSync (which are not provided by the shared fsMockFactory).
*/

// Full fs mock — must be hoisted before any imports that use fs
jest.mock('fs', () => {
const actual = jest.requireActual<typeof import('fs')>('fs');
return {
...actual,
mkdirSync: jest.fn(),
chmodSync: jest.fn(),
chownSync: jest.fn(),
existsSync: jest.fn(),
lstatSync: jest.fn(),
statSync: jest.fn(),
writeFileSync: jest.fn(),
copyFileSync: jest.fn(),
readFileSync: jest.fn(),
readdirSync: jest.fn(),
mkdtempSync: jest.fn(),
rmSync: jest.fn(),
};
});

jest.mock('./ssl-bump', () => ({
isOpenSslAvailable: jest.fn(),
generateSessionCa: jest.fn(),
initSslDb: jest.fn(),
cleanupSslKeyMaterial: jest.fn(),
unmountSslTmpfs: jest.fn(),
}));

jest.mock('./host-env', () => ({
SQUID_PORT: 3128,
stripScheme: jest.fn((v: string) => v),
getSafeHostUid: jest.fn().mockReturnValue('1000'),
getSafeHostGid: jest.fn().mockReturnValue('1000'),
getRealUserHome: jest.fn().mockReturnValue('/home/test'),
}));

jest.mock('./host-identity', () => ({
getSafeHostUid: jest.fn().mockReturnValue('1000'),
getSafeHostGid: jest.fn().mockReturnValue('1000'),
getRealUserHome: jest.fn().mockReturnValue('/home/test'),
}));

jest.mock('./squid-config', () => ({
generateSquidConfig: jest.fn().mockReturnValue('# mock squid config'),
generatePolicyManifest: jest.fn().mockReturnValue({}),
}));

jest.mock('./compose-generator', () => ({
generateDockerCompose: jest.fn().mockReturnValue({ services: {}, version: '3' }),
redactDockerComposeSecrets: jest.fn().mockReturnValue({ services: {}, version: '3' }),
}));

jest.mock('./domain-matchers', () => ({
parseUrlPatterns: jest.fn().mockReturnValue([]),
}));

import * as fs from 'fs';
import * as path from 'path';
import { configWriterTestHelpers } from './config-writer';

const { validateAndPrepareWorkDir, copySeccompProfile, writeAuditArtifacts } =
configWriterTestHelpers;

const fsMock = fs as jest.Mocked<typeof fs>;

function makeConfig(workDir = '/tmp/test-workdir', overrides = {}) {
return {
workDir,
sslBump: false,
allowedDomains: [],
agentCommand: 'echo test',
logLevel: 'info' as const,
keepContainers: false,
buildLocal: false,
imageRegistry: 'ghcr.io/github/gh-aw-firewall',
imageTag: 'latest',
...overrides,
};
}

beforeEach(() => {
jest.clearAllMocks();
// Default: mkdirSync returns undefined (dir already existed → workDirCreated=false)
fsMock.mkdirSync.mockReturnValue(undefined);
// Default: not a symlink, is a directory
fsMock.lstatSync.mockReturnValue({ isSymbolicLink: () => false } as fs.Stats);
fsMock.statSync.mockReturnValue({ isDirectory: () => true } as fs.Stats);
});

// ─── validateAndPrepareWorkDir — non-directory guard ─────────────────────────

describe('config-writer: validateAndPrepareWorkDir — non-directory workDir (line 58)', () => {
it('throws when workDir exists but is not a directory', () => {
fsMock.lstatSync.mockReturnValueOnce({ isSymbolicLink: () => false } as fs.Stats);
fsMock.statSync.mockReturnValueOnce({ isDirectory: () => false } as fs.Stats);

const workDir = '/tmp/test-workdir';
expect(() =>
validateAndPrepareWorkDir(makeConfig(workDir))
).toThrow(`Expected directory but found non-directory path: ${workDir}`);
});

it('does not throw when workDir is a valid directory', () => {
fsMock.lstatSync.mockReturnValueOnce({ isSymbolicLink: () => false } as fs.Stats);
fsMock.statSync.mockReturnValueOnce({ isDirectory: () => true } as fs.Stats);

expect(() => validateAndPrepareWorkDir(makeConfig())).not.toThrow();
expect(fsMock.chmodSync).toHaveBeenCalled();
});
});

// ─── copySeccompProfile — alt-path and missing branches ──────────────────────

describe('config-writer: copySeccompProfile — alt-path fallback (lines 95-97)', () => {
it('copies from the alt path when primary path does not exist', () => {
fsMock.existsSync
.mockReturnValueOnce(false) // primary containers/ path
.mockReturnValueOnce(true); // dist/../containers/ alt path

const config = makeConfig();
copySeccompProfile(config);

expect(fsMock.copyFileSync).toHaveBeenCalledTimes(1);
expect(fsMock.copyFileSync).toHaveBeenCalledWith(
expect.stringContaining('seccomp-profile.json'),
expect.stringContaining('seccomp-profile.json'),
);
});

it('throws when neither primary nor alt seccomp path exists', () => {
fsMock.existsSync
.mockReturnValueOnce(false) // primary path
.mockReturnValueOnce(false); // alt path

expect(() => copySeccompProfile(makeConfig())).toThrow('Seccomp profile not found');
expect(fsMock.copyFileSync).not.toHaveBeenCalled();
});

it('copies from the primary path when it exists', () => {
fsMock.existsSync.mockReturnValueOnce(true); // primary path exists

copySeccompProfile(makeConfig());

expect(fsMock.copyFileSync).toHaveBeenCalledTimes(1);
});
});

// ─── writeAuditArtifacts — non-directory auditDir guard ──────────────────────

describe('config-writer: writeAuditArtifacts — non-directory auditDir (line 162)', () => {
it('throws when auditDir exists but is not a directory', () => {
fsMock.lstatSync.mockReturnValue({ isSymbolicLink: () => false } as fs.Stats);
fsMock.statSync.mockReturnValue({ isDirectory: () => false } as fs.Stats);

const workDir = '/tmp/test-workdir';
const auditPath = path.join(workDir, 'audit');

expect(() =>
writeAuditArtifacts(makeConfig(workDir), {} as any, {} as any, '# squid')
).toThrow(`Expected directory but found non-directory path: ${auditPath}`);
});

it('writes audit artifacts when auditDir is valid', () => {
fsMock.lstatSync.mockReturnValue({ isSymbolicLink: () => false } as fs.Stats);
fsMock.statSync.mockReturnValue({ isDirectory: () => true } as fs.Stats);

expect(() =>
writeAuditArtifacts(makeConfig(), {} as any, { services: {}, version: '3' } as any, '# squid')
).not.toThrow();

expect(fsMock.writeFileSync).toHaveBeenCalled();
});
});
132 changes: 132 additions & 0 deletions src/coverage-new-branches.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Branch and line coverage for previously-uncovered paths in:
* - src/host-env.ts (stripScheme error branch)
* - src/commands/validators/agent-options.ts
* (env success path, invalid env-file, invalid mount,
* valid mount success path, enableDlp log path)
*/

// ─── host-env.ts ─────────────────────────────────────────────────────────────

import { stripScheme } from './host-env';

describe('host-env: stripScheme — error branch (line 81)', () => {
it('returns trimmed value unchanged when the URL constructor throws on the candidate', () => {
// A bare string with spaces produces "https://not a valid url" which is invalid.
const value = 'not a valid url with spaces';
expect(stripScheme(value)).toBe(value.trim());
});

it('returns empty string for empty input', () => {
expect(stripScheme('')).toBe('');
expect(stripScheme(' ')).toBe('');
});

it('strips scheme from a valid https URL', () => {
expect(stripScheme('https://api.example.com/path')).toBe('api.example.com');
});

it('handles bare hostname without scheme', () => {
expect(stripScheme('api.example.com')).toBe('api.example.com');
});
});

// ─── agent-options.ts ────────────────────────────────────────────────────────

jest.mock('./option-parsers', () => ({
parseEnvironmentVariables: jest.fn(),
parseVolumeMounts: jest.fn(),
}));

jest.mock('fs', () => ({
...jest.requireActual('fs'),
existsSync: jest.fn(),
}));

import { validateAgentOptions } from './commands/validators/agent-options';
import { parseEnvironmentVariables, parseVolumeMounts } from './option-parsers';

const fsMock = jest.requireMock('fs') as { existsSync: jest.Mock };
const parseEnvMock = parseEnvironmentVariables as jest.MockedFunction<typeof parseEnvironmentVariables>;
const parseMountMock = parseVolumeMounts as jest.MockedFunction<typeof parseVolumeMounts>;

describe('validateAgentOptions', () => {
const mockExit = jest.spyOn(process, 'exit').mockImplementation((() => {
throw new Error('process.exit called');
}) as any);

afterEach(() => {
jest.clearAllMocks();
});

afterAll(() => {
mockExit.mockRestore();
});

it('sets additionalEnv when parseEnvironmentVariables succeeds (line 45)', () => {
parseEnvMock.mockReturnValue({ success: true, env: { FOO: 'bar' } });

const result = validateAgentOptions({ env: ['FOO=bar'] });
expect(result.additionalEnv).toEqual({ FOO: 'bar' });
expect(mockExit).not.toHaveBeenCalled();
});

it('exits when parseEnvironmentVariables fails (branch 39)', () => {
parseEnvMock.mockReturnValue({ success: false, invalidVar: 'BADVAR' });

expect(() => validateAgentOptions({ env: ['BADVAR'] })).toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(1);
});

it('exits when envFile does not exist (branch 50)', () => {
fsMock.existsSync.mockReturnValue(false);

expect(() =>
validateAgentOptions({ envFile: '/no/such/file.env' })
).toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(1);
});

it('does not exit when envFile exists (branch 50 — true path)', () => {
fsMock.existsSync.mockReturnValue(true);

const result = validateAgentOptions({ envFile: '/valid/file.env' });
expect(mockExit).not.toHaveBeenCalled();
expect(result.additionalEnv).toEqual({});
});

it('sets volumeMounts when parseVolumeMounts succeeds (lines 67-68)', () => {
parseMountMock.mockReturnValue({ success: true, mounts: ['/host:/container:ro'] });

const result = validateAgentOptions({ mount: ['/host:/container:ro'] });
expect(result.volumeMounts).toEqual(['/host:/container:ro']);
expect(mockExit).not.toHaveBeenCalled();
});

it('exits when parseVolumeMounts fails (branch 62)', () => {
parseMountMock.mockReturnValue({
success: false,
invalidMount: 'bad-mount',
reason: 'missing colon separator',
});

expect(() =>
validateAgentOptions({ mount: ['bad-mount'] })
).toThrow('process.exit called');
expect(mockExit).toHaveBeenCalledWith(1);
});

it('does not exit when enableDlp is true (line 141, branch 140)', () => {
const result = validateAgentOptions({ enableDlp: true });
expect(mockExit).not.toHaveBeenCalled();
expect(result.additionalEnv).toEqual({});
});

it('returns empty result when no options are provided', () => {
const result = validateAgentOptions({});
expect(result.additionalEnv).toEqual({});
expect(result.volumeMounts).toBeUndefined();
expect(result.allowedUrls).toBeUndefined();
expect(mockExit).not.toHaveBeenCalled();
});
});
Loading