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
279 changes: 279 additions & 0 deletions src/compose-generator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
import { generateDockerCompose, ACT_PRESET_BASE_IMAGE } from './docker-manager';
import { WrapperConfig } from './types';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

// Create mock functions
const mockExecaFn = jest.fn();
const mockExecaSync = jest.fn();

// Mock execa module
jest.mock('execa', () => {
const fn = (...args: any[]) => mockExecaFn(...args);
fn.sync = (...args: any[]) => mockExecaSync(...args);
return fn;
});

const baseConfig: Omit<WrapperConfig, 'workDir'> = {
allowedDomains: ['github.com', 'npmjs.org'],
agentCommand: 'echo "test"',
logLevel: 'info',
keepContainers: false,
buildLocal: false,
imageRegistry: 'ghcr.io/github/gh-aw-firewall',
imageTag: 'latest',
};

let mockConfig: WrapperConfig;

const mockNetworkConfig = {
subnet: '172.30.0.0/24',
squidIp: '172.30.0.10',
agentIp: '172.30.0.20',
};

describe('generateDockerCompose', () => {
beforeEach(() => {
mockConfig = { ...baseConfig, workDir: fs.mkdtempSync(path.join(os.tmpdir(), 'awf-test-')) };
});

afterEach(() => {
fs.rmSync(mockConfig.workDir, { recursive: true, force: true });
});

it('should generate docker-compose config with GHCR images by default', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);

expect(result.services['squid-proxy'].image).toBe('ghcr.io/github/gh-aw-firewall/squid:latest');
expect(result.services.agent.image).toBe('ghcr.io/github/gh-aw-firewall/agent:latest');
expect(result.services['squid-proxy'].build).toBeUndefined();
expect(result.services.agent.build).toBeUndefined();
});

it('should use local build when buildLocal is true', () => {
const localConfig = { ...mockConfig, buildLocal: true };
const result = generateDockerCompose(localConfig, mockNetworkConfig);

expect(result.services['squid-proxy'].build).toBeDefined();
expect(result.services.agent.build).toBeDefined();
expect(result.services['squid-proxy'].image).toBeUndefined();
expect(result.services.agent.image).toBeUndefined();
});

it('should pass BASE_IMAGE build arg when custom agentImage is specified with --build-local', () => {
const customImageConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'ghcr.io/catthehacker/ubuntu:runner-22.04',
};
const result = generateDockerCompose(customImageConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ghcr.io/catthehacker/ubuntu:runner-22.04');
});

it('should not include BASE_IMAGE build arg when using default agentImage with --build-local', () => {
const localConfig = { ...mockConfig, buildLocal: true, agentImage: 'default' };
const result = generateDockerCompose(localConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
// BASE_IMAGE should not be set when using the default preset
expect(result.services.agent.build?.args?.BASE_IMAGE).toBeUndefined();
});

it('should not include BASE_IMAGE build arg when agentImage is undefined with --build-local', () => {
const localConfig = { ...mockConfig, buildLocal: true };
// agentImage is not set, should default to 'default' preset
const result = generateDockerCompose(localConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
// BASE_IMAGE should not be set when using the default (undefined means 'default')
expect(result.services.agent.build?.args?.BASE_IMAGE).toBeUndefined();
});

it('should pass BASE_IMAGE build arg when agentImage with SHA256 digest is specified', () => {
const customImageConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'ghcr.io/catthehacker/ubuntu:full-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
};
const result = generateDockerCompose(customImageConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ghcr.io/catthehacker/ubuntu:full-22.04@sha256:a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1');
});

it('should use act base image when agentImage is "act" preset with --build-local', () => {
const actPresetConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'act',
};
const result = generateDockerCompose(actPresetConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
// When using 'act' preset with --build-local, should use the catthehacker act image
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe(ACT_PRESET_BASE_IMAGE);
});

it('should use agent-act GHCR image when agentImage is "act" preset without --build-local', () => {
const actPresetConfig = {
...mockConfig,
agentImage: 'act',
};
const result = generateDockerCompose(actPresetConfig, mockNetworkConfig);

expect(result.services.agent.image).toBe('ghcr.io/github/gh-aw-firewall/agent-act:latest');
expect(result.services.agent.build).toBeUndefined();
});

it('should use agent GHCR image when agentImage is "default" preset', () => {
const defaultPresetConfig = {
...mockConfig,
agentImage: 'default',
};
const result = generateDockerCompose(defaultPresetConfig, mockNetworkConfig);

expect(result.services.agent.image).toBe('ghcr.io/github/gh-aw-firewall/agent:latest');
expect(result.services.agent.build).toBeUndefined();
});

it('should use agent GHCR image when agentImage is undefined', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);

expect(result.services.agent.image).toBe('ghcr.io/github/gh-aw-firewall/agent:latest');
expect(result.services.agent.build).toBeUndefined();
});

it('should use custom registry and tag with act preset', () => {
const customConfig = {
...mockConfig,
agentImage: 'act',
imageRegistry: 'docker.io/myrepo',
imageTag: 'v1.0.0',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services['squid-proxy'].image).toBe('docker.io/myrepo/squid:v1.0.0');
expect(result.services.agent.image).toBe('docker.io/myrepo/agent-act:v1.0.0');
});

it('should use custom registry and tag', () => {
const customConfig = {
...mockConfig,
imageRegistry: 'docker.io/myrepo',
imageTag: 'v1.0.0',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services['squid-proxy'].image).toBe('docker.io/myrepo/squid:v1.0.0');
expect(result.services.agent.image).toBe('docker.io/myrepo/agent:v1.0.0');
});

it('should use custom registry and tag with default preset explicitly set', () => {
const customConfig = {
...mockConfig,
agentImage: 'default',
imageRegistry: 'docker.io/myrepo',
imageTag: 'v2.0.0',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services.agent.image).toBe('docker.io/myrepo/agent:v2.0.0');
expect(result.services.agent.build).toBeUndefined();
});

it('should append per-image digests from image-tag metadata', () => {
const customConfig = {
...mockConfig,
enableApiProxy: true,
imageTag: [
'v1.0.0',
'squid=sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'agent=sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
'api-proxy=sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc',
].join(','),
};
const networkWithProxy = {
...mockNetworkConfig,
proxyIp: '172.30.0.30',
};
const result = generateDockerCompose(customConfig, networkWithProxy);

expect(result.services['squid-proxy'].image).toBe(
'ghcr.io/github/gh-aw-firewall/squid:v1.0.0@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
);
expect(result.services.agent.image).toBe(
'ghcr.io/github/gh-aw-firewall/agent:v1.0.0@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
);
expect(result.services['iptables-init'].image).toBe(
'ghcr.io/github/gh-aw-firewall/agent:v1.0.0@sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
);
expect(result.services['api-proxy'].image).toBe(
'ghcr.io/github/gh-aw-firewall/api-proxy:v1.0.0@sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
);
});

it('should build locally with custom catthehacker full image', () => {
const customConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'ghcr.io/catthehacker/ubuntu:full-24.04',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ghcr.io/catthehacker/ubuntu:full-24.04');
expect(result.services.agent.image).toBeUndefined();
});

it('should build locally with custom ubuntu image', () => {
const customConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'ubuntu:24.04',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services.agent.build).toBeDefined();
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe('ubuntu:24.04');
});

it('should include USER_UID and USER_GID in build args with custom image', () => {
const customConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'ghcr.io/catthehacker/ubuntu:runner-22.04',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services.agent.build?.args?.USER_UID).toBeDefined();
expect(result.services.agent.build?.args?.USER_GID).toBeDefined();
});

it('should include USER_UID and USER_GID in build args with act preset', () => {
const customConfig = {
...mockConfig,
buildLocal: true,
agentImage: 'act',
};
const result = generateDockerCompose(customConfig, mockNetworkConfig);

expect(result.services.agent.build?.args?.USER_UID).toBeDefined();
expect(result.services.agent.build?.args?.USER_GID).toBeDefined();
expect(result.services.agent.build?.args?.BASE_IMAGE).toBe(ACT_PRESET_BASE_IMAGE);
});

it('should configure network with correct IPs', () => {
const result = generateDockerCompose(mockConfig, mockNetworkConfig);

expect(result.networks['awf-net'].external).toBe(true);

const squidNetworks = result.services['squid-proxy'].networks as { [key: string]: { ipv4_address?: string } };
expect(squidNetworks['awf-net'].ipv4_address).toBe('172.30.0.10');

const agentNetworks = result.services.agent.networks as { [key: string]: { ipv4_address?: string } };
expect(agentNetworks['awf-net'].ipv4_address).toBe('172.30.0.20');
});
});
Loading
Loading