@ordojs/cli
Version:
Command-line interface for OrdoJS framework
273 lines (221 loc) • 11 kB
text/typescript
/**
* @fileoverview Tests for the infrastructure command
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { CLIError } from '../utils/index.js';
import { infrastructureCommand } from './infrastructure.js';
// Mock the generators
vi.mock('../utils/deployment/docker-generator.js', () => ({
DockerGenerator: vi.fn().mockImplementation(() => ({
generateDockerfile: vi.fn().mockReturnValue('FROM node:18-alpine'),
generateDockerCompose: vi.fn().mockReturnValue('version: \'3.8\''),
generateDockerignore: vi.fn().mockReturnValue('node_modules'),
generateKubernetesManifests: vi.fn().mockReturnValue({
deployment: 'apiVersion: apps/v1',
service: 'apiVersion: v1',
ingress: 'apiVersion: networking.k8s.io/v1',
configMap: 'apiVersion: v1',
secret: ''
})
}))
}));
vi.mock('../utils/deployment/cicd-generator.js', () => ({
CICDGenerator: vi.fn().mockImplementation(() => ({
generatePipeline: vi.fn().mockReturnValue('name: OrdoJS CI/CD Pipeline')
}))
}));
vi.mock('../utils/deployment/monitoring-generator.js', () => ({
MonitoringGenerator: vi.fn().mockImplementation(() => ({
generateMonitoringConfig: vi.fn().mockReturnValue({
logger: 'import winston',
metrics: 'import prometheus',
tracing: 'import opentelemetry',
healthCheck: 'import healthCheck',
errorHandler: 'import errorHandler',
dockerCompose: 'version: \'3.8\'',
kubernetes: 'apiVersion: v1'
})
}))
}));
// Mock file system operations
vi.mock('../utils/fs.js', () => ({
writeFile: vi.fn().mockResolvedValue(undefined),
mkdir: vi.fn().mockResolvedValue(undefined)
}));
describe('infrastructureCommand', () => {
const mockOptions = {
type: 'all',
platform: 'github',
output: '.',
nodeVersion: '18',
port: 3000,
multiStage: true,
healthCheck: true,
autoDeploy: false,
monitoring: true,
env: {
NODE_ENV: 'production',
PORT: '3000'
}
};
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Docker infrastructure generation', () => {
it('should generate Docker infrastructure when type is docker', async () => {
const options = { ...mockOptions, type: 'docker' };
await infrastructureCommand(options);
const { writeFile } = await import('../utils/fs.js');
expect(writeFile).toHaveBeenCalledWith('./Dockerfile', 'FROM node:18-alpine');
expect(writeFile).toHaveBeenCalledWith('./docker-compose.yml', 'version: \'3.8\'');
expect(writeFile).toHaveBeenCalledWith('./.dockerignore', 'node_modules');
});
it('should generate Kubernetes manifests', async () => {
const options = { ...mockOptions, type: 'docker' };
await infrastructureCommand(options);
const { writeFile, mkdir } = await import('../utils/fs.js');
expect(mkdir).toHaveBeenCalledWith('./k8s', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./k8s/deployment.yaml', 'apiVersion: apps/v1');
expect(writeFile).toHaveBeenCalledWith('./k8s/service.yaml', 'apiVersion: v1');
expect(writeFile).toHaveBeenCalledWith('./k8s/ingress.yaml', 'apiVersion: networking.k8s.io/v1');
expect(writeFile).toHaveBeenCalledWith('./k8s/configmap.yaml', 'apiVersion: v1');
});
it('should use custom Node.js version', async () => {
const options = { ...mockOptions, type: 'docker', nodeVersion: '20' };
await infrastructureCommand(options);
const { DockerGenerator } = await import('../utils/deployment/docker-generator.js');
const mockGenerator = DockerGenerator.mock.instances[0];
expect(mockGenerator.generateDockerfile).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({ nodeVersion: '20' })
);
});
it('should use custom port', async () => {
const options = { ...mockOptions, type: 'docker', port: 8080 };
await infrastructureCommand(options);
const { DockerGenerator } = await import('../utils/deployment/docker-generator.js');
const mockGenerator = DockerGenerator.mock.instances[0];
expect(mockGenerator.generateDockerfile).toHaveBeenCalledWith(
expect.any(Object),
expect.objectContaining({ port: 8080 })
);
});
});
describe('CI/CD infrastructure generation', () => {
it('should generate GitHub Actions when platform is github', async () => {
const options = { ...mockOptions, type: 'cicd', platform: 'github' };
await infrastructureCommand(options);
const { writeFile, mkdir } = await import('../utils/fs.js');
expect(mkdir).toHaveBeenCalledWith('./.github/workflows', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./.github/workflows/ci-cd.yml', 'name: OrdoJS CI/CD Pipeline');
});
it('should generate GitLab CI when platform is gitlab', async () => {
const options = { ...mockOptions, type: 'cicd', platform: 'gitlab' };
await infrastructureCommand(options);
const { writeFile } = await import('../utils/fs.js');
expect(writeFile).toHaveBeenCalledWith('./.gitlab-ci.yml', 'name: OrdoJS CI/CD Pipeline');
});
it('should generate Jenkins pipeline when platform is jenkins', async () => {
const options = { ...mockOptions, type: 'cicd', platform: 'jenkins' };
await infrastructureCommand(options);
const { writeFile } = await import('../utils/fs.js');
expect(writeFile).toHaveBeenCalledWith('./Jenkinsfile', 'name: OrdoJS CI/CD Pipeline');
});
it('should throw error for unsupported platform', async () => {
const options = { ...mockOptions, type: 'cicd', platform: 'unsupported' };
await expect(infrastructureCommand(options)).rejects.toThrow(CLIError);
});
});
describe('Monitoring infrastructure generation', () => {
it('should generate monitoring infrastructure when monitoring is enabled', async () => {
const options = { ...mockOptions, type: 'monitoring', monitoring: true };
await infrastructureCommand(options);
const { writeFile, mkdir } = await import('../utils/fs.js');
expect(mkdir).toHaveBeenCalledWith('./monitoring', { recursive: true });
expect(mkdir).toHaveBeenCalledWith('./src/monitoring', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/logger.js', 'import winston');
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/metrics.js', 'import prometheus');
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/tracing.js', 'import opentelemetry');
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/health-check.js', 'import healthCheck');
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/error-handler.js', 'import errorHandler');
expect(writeFile).toHaveBeenCalledWith('./docker-compose.monitoring.yml', 'version: \'3.8\'');
expect(writeFile).toHaveBeenCalledWith('./k8s/monitoring/monitoring.yml', 'apiVersion: v1');
});
it('should not generate monitoring infrastructure when monitoring is disabled', async () => {
const options = { ...mockOptions, type: 'monitoring', monitoring: false };
await infrastructureCommand(options);
const { writeFile } = await import('../utils/fs.js');
// Should not call writeFile for monitoring files
expect(writeFile).not.toHaveBeenCalledWith('./src/monitoring/logger.js', expect.any(String));
});
});
describe('Complete infrastructure generation', () => {
it('should generate all infrastructure components when type is all', async () => {
const options = { ...mockOptions, type: 'all' };
await infrastructureCommand(options);
const { writeFile, mkdir } = await import('../utils/fs.js');
// Check Docker files
expect(writeFile).toHaveBeenCalledWith('./Dockerfile', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./docker-compose.yml', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./.dockerignore', expect.any(String));
// Check Kubernetes files
expect(mkdir).toHaveBeenCalledWith('./k8s', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./k8s/deployment.yaml', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./k8s/service.yaml', expect.any(String));
// Check CI/CD files
expect(mkdir).toHaveBeenCalledWith('./.github/workflows', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./.github/workflows/ci-cd.yml', expect.any(String));
// Check monitoring files
expect(mkdir).toHaveBeenCalledWith('./monitoring', { recursive: true });
expect(mkdir).toHaveBeenCalledWith('./src/monitoring', { recursive: true });
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/logger.js', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/metrics.js', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/tracing.js', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/health-check.js', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./src/monitoring/error-handler.js', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./docker-compose.monitoring.yml', expect.any(String));
expect(writeFile).toHaveBeenCalledWith('./k8s/monitoring/monitoring.yml', expect.any(String));
});
});
describe('Environment variables', () => {
it('should pass environment variables to generators', async () => {
const options = {
...mockOptions,
type: 'docker',
env: {
NODE_ENV: 'production',
API_KEY: 'secret',
DATABASE_URL: 'postgres://localhost:5432/mydb'
}
};
await infrastructureCommand(options);
const { DockerGenerator } = await import('../utils/deployment/docker-generator.js');
const mockGenerator = DockerGenerator.mock.instances[0];
expect(mockGenerator.generateDockerfile).toHaveBeenCalledWith(
expect.objectContaining({
env: {
NODE_ENV: 'production',
API_KEY: 'secret',
DATABASE_URL: 'postgres://localhost:5432/mydb'
}
}),
expect.any(Object)
);
});
});
describe('Error handling', () => {
it('should throw CLIError when file writing fails', async () => {
const { writeFile } = await import('../utils/fs.js');
vi.mocked(writeFile).mockRejectedValueOnce(new Error('Write failed'));
await expect(infrastructureCommand(mockOptions)).rejects.toThrow(CLIError);
});
it('should throw CLIError when directory creation fails', async () => {
const { mkdir } = await import('../utils/fs.js');
vi.mocked(mkdir).mockRejectedValueOnce(new Error('Mkdir failed'));
await expect(infrastructureCommand(mockOptions)).rejects.toThrow(CLIError);
});
});
});