@build-astron-co/nimbus 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +628 -0
- package/bin/nimbus +38 -0
- package/package.json +80 -0
- package/src/__tests__/app.test.ts +76 -0
- package/src/__tests__/audit.test.ts +877 -0
- package/src/__tests__/circuit-breaker.test.ts +116 -0
- package/src/__tests__/cli-run.test.ts +115 -0
- package/src/__tests__/context-manager.test.ts +502 -0
- package/src/__tests__/context.test.ts +242 -0
- package/src/__tests__/enterprise.test.ts +401 -0
- package/src/__tests__/generator.test.ts +433 -0
- package/src/__tests__/hooks.test.ts +582 -0
- package/src/__tests__/init.test.ts +436 -0
- package/src/__tests__/intent-parser.test.ts +229 -0
- package/src/__tests__/llm-router.test.ts +209 -0
- package/src/__tests__/lsp.test.ts +293 -0
- package/src/__tests__/modes.test.ts +336 -0
- package/src/__tests__/permissions.test.ts +338 -0
- package/src/__tests__/serve.test.ts +275 -0
- package/src/__tests__/sessions.test.ts +227 -0
- package/src/__tests__/sharing.test.ts +288 -0
- package/src/__tests__/snapshots.test.ts +581 -0
- package/src/__tests__/state-db.test.ts +334 -0
- package/src/__tests__/stream-with-tools.test.ts +732 -0
- package/src/__tests__/subagents.test.ts +176 -0
- package/src/__tests__/system-prompt.test.ts +169 -0
- package/src/__tests__/tool-converter.test.ts +256 -0
- package/src/__tests__/tool-schemas.test.ts +397 -0
- package/src/__tests__/tools.test.ts +143 -0
- package/src/__tests__/version.test.ts +49 -0
- package/src/agent/compaction-agent.ts +227 -0
- package/src/agent/context-manager.ts +435 -0
- package/src/agent/context.ts +427 -0
- package/src/agent/deploy-preview.ts +426 -0
- package/src/agent/index.ts +68 -0
- package/src/agent/loop.ts +717 -0
- package/src/agent/modes.ts +429 -0
- package/src/agent/permissions.ts +466 -0
- package/src/agent/subagents/base.ts +116 -0
- package/src/agent/subagents/cost.ts +51 -0
- package/src/agent/subagents/explore.ts +42 -0
- package/src/agent/subagents/general.ts +54 -0
- package/src/agent/subagents/index.ts +102 -0
- package/src/agent/subagents/infra.ts +59 -0
- package/src/agent/subagents/security.ts +69 -0
- package/src/agent/system-prompt.ts +436 -0
- package/src/app.ts +122 -0
- package/src/audit/activity-log.ts +290 -0
- package/src/audit/compliance-checker.ts +540 -0
- package/src/audit/cost-tracker.ts +318 -0
- package/src/audit/index.ts +23 -0
- package/src/audit/security-scanner.ts +596 -0
- package/src/auth/guard.ts +75 -0
- package/src/auth/index.ts +56 -0
- package/src/auth/oauth.ts +455 -0
- package/src/auth/providers.ts +470 -0
- package/src/auth/sso.ts +113 -0
- package/src/auth/store.ts +505 -0
- package/src/auth/types.ts +187 -0
- package/src/build.ts +141 -0
- package/src/cli/index.ts +16 -0
- package/src/cli/init.ts +854 -0
- package/src/cli/openapi-spec.ts +356 -0
- package/src/cli/run.ts +237 -0
- package/src/cli/serve-auth.ts +80 -0
- package/src/cli/serve.ts +462 -0
- package/src/cli/web.ts +67 -0
- package/src/cli.ts +1417 -0
- package/src/clients/core-engine-client.ts +227 -0
- package/src/clients/enterprise-client.ts +334 -0
- package/src/clients/generator-client.ts +351 -0
- package/src/clients/git-client.ts +627 -0
- package/src/clients/github-client.ts +410 -0
- package/src/clients/helm-client.ts +504 -0
- package/src/clients/index.ts +80 -0
- package/src/clients/k8s-client.ts +497 -0
- package/src/clients/llm-client.ts +161 -0
- package/src/clients/rest-client.ts +130 -0
- package/src/clients/service-discovery.ts +33 -0
- package/src/clients/terraform-client.ts +482 -0
- package/src/clients/tools-client.ts +1843 -0
- package/src/clients/ws-client.ts +115 -0
- package/src/commands/analyze/index.ts +352 -0
- package/src/commands/apply/helm.ts +473 -0
- package/src/commands/apply/index.ts +213 -0
- package/src/commands/apply/k8s.ts +454 -0
- package/src/commands/apply/terraform.ts +582 -0
- package/src/commands/ask.ts +167 -0
- package/src/commands/audit/index.ts +238 -0
- package/src/commands/auth-cloud.ts +294 -0
- package/src/commands/auth-list.ts +134 -0
- package/src/commands/auth-profile.ts +121 -0
- package/src/commands/auth-status.ts +141 -0
- package/src/commands/aws/ec2.ts +501 -0
- package/src/commands/aws/iam.ts +397 -0
- package/src/commands/aws/index.ts +133 -0
- package/src/commands/aws/lambda.ts +396 -0
- package/src/commands/aws/rds.ts +439 -0
- package/src/commands/aws/s3.ts +439 -0
- package/src/commands/aws/vpc.ts +393 -0
- package/src/commands/aws-discover.ts +649 -0
- package/src/commands/aws-terraform.ts +805 -0
- package/src/commands/azure/aks.ts +376 -0
- package/src/commands/azure/functions.ts +253 -0
- package/src/commands/azure/index.ts +116 -0
- package/src/commands/azure/storage.ts +478 -0
- package/src/commands/azure/vm.ts +355 -0
- package/src/commands/billing/index.ts +256 -0
- package/src/commands/chat.ts +314 -0
- package/src/commands/config.ts +346 -0
- package/src/commands/cost/cloud-cost-estimator.ts +266 -0
- package/src/commands/cost/estimator.ts +79 -0
- package/src/commands/cost/index.ts +594 -0
- package/src/commands/cost/parsers/terraform.ts +273 -0
- package/src/commands/cost/parsers/types.ts +25 -0
- package/src/commands/cost/pricing/aws.ts +544 -0
- package/src/commands/cost/pricing/azure.ts +499 -0
- package/src/commands/cost/pricing/gcp.ts +396 -0
- package/src/commands/cost/pricing/index.ts +40 -0
- package/src/commands/demo.ts +250 -0
- package/src/commands/doctor.ts +794 -0
- package/src/commands/drift/index.ts +439 -0
- package/src/commands/explain.ts +277 -0
- package/src/commands/feedback.ts +389 -0
- package/src/commands/fix.ts +324 -0
- package/src/commands/fs/index.ts +402 -0
- package/src/commands/gcp/compute.ts +325 -0
- package/src/commands/gcp/functions.ts +271 -0
- package/src/commands/gcp/gke.ts +438 -0
- package/src/commands/gcp/iam.ts +344 -0
- package/src/commands/gcp/index.ts +129 -0
- package/src/commands/gcp/storage.ts +284 -0
- package/src/commands/generate-helm.ts +1249 -0
- package/src/commands/generate-k8s.ts +1560 -0
- package/src/commands/generate-terraform.ts +1460 -0
- package/src/commands/gh/index.ts +863 -0
- package/src/commands/git/index.ts +1343 -0
- package/src/commands/helm/index.ts +1126 -0
- package/src/commands/help.ts +539 -0
- package/src/commands/history.ts +142 -0
- package/src/commands/import.ts +868 -0
- package/src/commands/index.ts +367 -0
- package/src/commands/init.ts +1046 -0
- package/src/commands/k8s/index.ts +1137 -0
- package/src/commands/login.ts +631 -0
- package/src/commands/logout.ts +83 -0
- package/src/commands/onboarding.ts +228 -0
- package/src/commands/plan/display.ts +279 -0
- package/src/commands/plan/index.ts +599 -0
- package/src/commands/preview.ts +452 -0
- package/src/commands/questionnaire.ts +1270 -0
- package/src/commands/resume.ts +55 -0
- package/src/commands/team/index.ts +346 -0
- package/src/commands/template.ts +232 -0
- package/src/commands/tf/index.ts +1034 -0
- package/src/commands/upgrade.ts +550 -0
- package/src/commands/usage/index.ts +134 -0
- package/src/commands/version.ts +170 -0
- package/src/compat/index.ts +2 -0
- package/src/compat/runtime.ts +12 -0
- package/src/compat/sqlite.ts +107 -0
- package/src/config/index.ts +17 -0
- package/src/config/manager.ts +530 -0
- package/src/config/safety-policy.ts +358 -0
- package/src/config/schema.ts +125 -0
- package/src/config/types.ts +527 -0
- package/src/context/context-db.ts +199 -0
- package/src/demo/index.ts +349 -0
- package/src/demo/scenarios/full-journey.ts +229 -0
- package/src/demo/scenarios/getting-started.ts +127 -0
- package/src/demo/scenarios/helm-release.ts +341 -0
- package/src/demo/scenarios/k8s-deployment.ts +194 -0
- package/src/demo/scenarios/terraform-vpc.ts +170 -0
- package/src/demo/types.ts +92 -0
- package/src/engine/cost-estimator.ts +438 -0
- package/src/engine/diagram-generator.ts +256 -0
- package/src/engine/drift-detector.ts +902 -0
- package/src/engine/executor.ts +1035 -0
- package/src/engine/index.ts +76 -0
- package/src/engine/orchestrator.ts +636 -0
- package/src/engine/planner.ts +720 -0
- package/src/engine/safety.ts +743 -0
- package/src/engine/verifier.ts +770 -0
- package/src/enterprise/audit.ts +348 -0
- package/src/enterprise/auth.ts +270 -0
- package/src/enterprise/billing.ts +822 -0
- package/src/enterprise/index.ts +17 -0
- package/src/enterprise/teams.ts +443 -0
- package/src/generator/best-practices.ts +1608 -0
- package/src/generator/helm.ts +630 -0
- package/src/generator/index.ts +37 -0
- package/src/generator/intent-parser.ts +514 -0
- package/src/generator/kubernetes.ts +976 -0
- package/src/generator/terraform.ts +1867 -0
- package/src/history/index.ts +8 -0
- package/src/history/manager.ts +322 -0
- package/src/history/types.ts +34 -0
- package/src/hooks/config.ts +432 -0
- package/src/hooks/engine.ts +391 -0
- package/src/hooks/index.ts +4 -0
- package/src/llm/auth-bridge.ts +198 -0
- package/src/llm/circuit-breaker.ts +140 -0
- package/src/llm/config-loader.ts +201 -0
- package/src/llm/cost-calculator.ts +171 -0
- package/src/llm/index.ts +8 -0
- package/src/llm/model-aliases.ts +115 -0
- package/src/llm/provider-registry.ts +63 -0
- package/src/llm/providers/anthropic.ts +433 -0
- package/src/llm/providers/bedrock.ts +477 -0
- package/src/llm/providers/google.ts +405 -0
- package/src/llm/providers/ollama.ts +767 -0
- package/src/llm/providers/openai-compatible.ts +340 -0
- package/src/llm/providers/openai.ts +328 -0
- package/src/llm/providers/openrouter.ts +338 -0
- package/src/llm/router.ts +1035 -0
- package/src/llm/types.ts +232 -0
- package/src/lsp/client.ts +298 -0
- package/src/lsp/languages.ts +116 -0
- package/src/lsp/manager.ts +278 -0
- package/src/mcp/client.ts +402 -0
- package/src/mcp/index.ts +5 -0
- package/src/mcp/manager.ts +133 -0
- package/src/nimbus.ts +214 -0
- package/src/plugins/index.ts +27 -0
- package/src/plugins/loader.ts +334 -0
- package/src/plugins/manager.ts +376 -0
- package/src/plugins/types.ts +284 -0
- package/src/scanners/cicd-scanner.ts +258 -0
- package/src/scanners/cloud-scanner.ts +466 -0
- package/src/scanners/framework-scanner.ts +469 -0
- package/src/scanners/iac-scanner.ts +388 -0
- package/src/scanners/index.ts +539 -0
- package/src/scanners/language-scanner.ts +276 -0
- package/src/scanners/package-manager-scanner.ts +277 -0
- package/src/scanners/types.ts +172 -0
- package/src/sessions/manager.ts +365 -0
- package/src/sessions/types.ts +44 -0
- package/src/sharing/sync.ts +296 -0
- package/src/sharing/viewer.ts +97 -0
- package/src/snapshots/index.ts +2 -0
- package/src/snapshots/manager.ts +530 -0
- package/src/state/artifacts.ts +147 -0
- package/src/state/audit.ts +137 -0
- package/src/state/billing.ts +240 -0
- package/src/state/checkpoints.ts +117 -0
- package/src/state/config.ts +67 -0
- package/src/state/conversations.ts +14 -0
- package/src/state/credentials.ts +154 -0
- package/src/state/db.ts +58 -0
- package/src/state/index.ts +26 -0
- package/src/state/messages.ts +115 -0
- package/src/state/projects.ts +123 -0
- package/src/state/schema.ts +236 -0
- package/src/state/sessions.ts +147 -0
- package/src/state/teams.ts +200 -0
- package/src/telemetry.ts +108 -0
- package/src/tools/aws-ops.ts +952 -0
- package/src/tools/azure-ops.ts +579 -0
- package/src/tools/file-ops.ts +593 -0
- package/src/tools/gcp-ops.ts +625 -0
- package/src/tools/git-ops.ts +773 -0
- package/src/tools/github-ops.ts +799 -0
- package/src/tools/helm-ops.ts +943 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/k8s-ops.ts +819 -0
- package/src/tools/schemas/converter.ts +184 -0
- package/src/tools/schemas/devops.ts +612 -0
- package/src/tools/schemas/index.ts +73 -0
- package/src/tools/schemas/standard.ts +1144 -0
- package/src/tools/schemas/types.ts +705 -0
- package/src/tools/terraform-ops.ts +862 -0
- package/src/types/ambient.d.ts +193 -0
- package/src/types/config.ts +83 -0
- package/src/types/drift.ts +116 -0
- package/src/types/enterprise.ts +335 -0
- package/src/types/index.ts +20 -0
- package/src/types/plan.ts +44 -0
- package/src/types/request.ts +65 -0
- package/src/types/response.ts +54 -0
- package/src/types/service.ts +51 -0
- package/src/ui/App.tsx +997 -0
- package/src/ui/DeployPreview.tsx +169 -0
- package/src/ui/Header.tsx +68 -0
- package/src/ui/InputBox.tsx +350 -0
- package/src/ui/MessageList.tsx +585 -0
- package/src/ui/PermissionPrompt.tsx +151 -0
- package/src/ui/StatusBar.tsx +158 -0
- package/src/ui/ToolCallDisplay.tsx +409 -0
- package/src/ui/chat-ui.ts +853 -0
- package/src/ui/index.ts +33 -0
- package/src/ui/ink/index.ts +711 -0
- package/src/ui/streaming.ts +176 -0
- package/src/ui/types.ts +57 -0
- package/src/utils/analytics.ts +72 -0
- package/src/utils/cost-warning.ts +27 -0
- package/src/utils/env.ts +46 -0
- package/src/utils/errors.ts +69 -0
- package/src/utils/event-bus.ts +38 -0
- package/src/utils/index.ts +24 -0
- package/src/utils/logger.ts +171 -0
- package/src/utils/rate-limiter.ts +121 -0
- package/src/utils/service-auth.ts +49 -0
- package/src/utils/validation.ts +53 -0
- package/src/version.ts +4 -0
- package/src/watcher/index.ts +163 -0
- package/src/wizard/approval.ts +383 -0
- package/src/wizard/index.ts +25 -0
- package/src/wizard/prompts.ts +338 -0
- package/src/wizard/types.ts +171 -0
- package/src/wizard/ui.ts +556 -0
- package/src/wizard/wizard.ts +304 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the generator modules:
|
|
3
|
+
* - src/generator/terraform.ts – TerraformProjectGenerator
|
|
4
|
+
* - src/generator/kubernetes.ts – KubernetesGenerator, createKubernetesGenerator
|
|
5
|
+
* - src/generator/helm.ts – HelmGenerator, createHelmGenerator
|
|
6
|
+
* - src/generator/best-practices.ts – BestPracticesEngine
|
|
7
|
+
*
|
|
8
|
+
* All generators are pure (no I/O beyond the in-process YAML serialiser), so
|
|
9
|
+
* tests run entirely in-memory and complete in milliseconds.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'bun:test';
|
|
13
|
+
import { TerraformProjectGenerator, type TerraformProjectConfig } from '../generator/terraform';
|
|
14
|
+
import {
|
|
15
|
+
KubernetesGenerator,
|
|
16
|
+
createKubernetesGenerator,
|
|
17
|
+
type K8sGeneratorConfig,
|
|
18
|
+
} from '../generator/kubernetes';
|
|
19
|
+
import { HelmGenerator, createHelmGenerator, type HelmChartConfig } from '../generator/helm';
|
|
20
|
+
import { BestPracticesEngine } from '../generator/best-practices';
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// TerraformProjectGenerator
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
describe('TerraformProjectGenerator', () => {
|
|
27
|
+
const baseConfig: TerraformProjectConfig = {
|
|
28
|
+
projectName: 'test-project',
|
|
29
|
+
provider: 'aws',
|
|
30
|
+
region: 'us-east-1',
|
|
31
|
+
components: ['vpc', 's3'],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
it('can be instantiated', () => {
|
|
35
|
+
const gen = new TerraformProjectGenerator();
|
|
36
|
+
expect(gen).toBeInstanceOf(TerraformProjectGenerator);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('generate() returns a GeneratedProject with files array', async () => {
|
|
40
|
+
const gen = new TerraformProjectGenerator();
|
|
41
|
+
const result = await gen.generate(baseConfig);
|
|
42
|
+
|
|
43
|
+
expect(result).toBeDefined();
|
|
44
|
+
expect(Array.isArray(result.files)).toBe(true);
|
|
45
|
+
expect(result.files.length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('generates the required root .tf files', async () => {
|
|
49
|
+
const gen = new TerraformProjectGenerator();
|
|
50
|
+
const { files } = await gen.generate(baseConfig);
|
|
51
|
+
const paths = files.map(f => f.path);
|
|
52
|
+
|
|
53
|
+
expect(paths).toContain('main.tf');
|
|
54
|
+
expect(paths).toContain('variables.tf');
|
|
55
|
+
expect(paths).toContain('outputs.tf');
|
|
56
|
+
expect(paths).toContain('versions.tf');
|
|
57
|
+
expect(paths).toContain('backend.tf');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('generates environment-specific tfvars files', async () => {
|
|
61
|
+
const gen = new TerraformProjectGenerator();
|
|
62
|
+
const { files } = await gen.generate(baseConfig);
|
|
63
|
+
const paths = files.map(f => f.path);
|
|
64
|
+
|
|
65
|
+
expect(paths).toContain('environments/dev/terraform.tfvars');
|
|
66
|
+
expect(paths).toContain('environments/staging/terraform.tfvars');
|
|
67
|
+
expect(paths).toContain('environments/prod/terraform.tfvars');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('generates module files for each requested component', async () => {
|
|
71
|
+
const gen = new TerraformProjectGenerator();
|
|
72
|
+
const { files } = await gen.generate(baseConfig);
|
|
73
|
+
const paths = files.map(f => f.path);
|
|
74
|
+
|
|
75
|
+
// vpc component
|
|
76
|
+
expect(paths).toContain('modules/vpc/main.tf');
|
|
77
|
+
expect(paths).toContain('modules/vpc/variables.tf');
|
|
78
|
+
expect(paths).toContain('modules/vpc/outputs.tf');
|
|
79
|
+
|
|
80
|
+
// s3 component
|
|
81
|
+
expect(paths).toContain('modules/s3/main.tf');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('generates a .gitignore file', async () => {
|
|
85
|
+
const gen = new TerraformProjectGenerator();
|
|
86
|
+
const { files } = await gen.generate(baseConfig);
|
|
87
|
+
const gitignore = files.find(f => f.path === '.gitignore');
|
|
88
|
+
|
|
89
|
+
expect(gitignore).toBeDefined();
|
|
90
|
+
expect(gitignore!.content).toContain('.terraform/');
|
|
91
|
+
expect(gitignore!.content).toContain('*.tfstate');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('validation report is valid when all required files are present', async () => {
|
|
95
|
+
const gen = new TerraformProjectGenerator();
|
|
96
|
+
const { validation } = await gen.generate(baseConfig);
|
|
97
|
+
|
|
98
|
+
expect(validation).toBeDefined();
|
|
99
|
+
expect(typeof validation.valid).toBe('boolean');
|
|
100
|
+
expect(typeof validation.summary.errors).toBe('number');
|
|
101
|
+
expect(typeof validation.summary.warnings).toBe('number');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('generated main.tf contains the provider block', async () => {
|
|
105
|
+
const gen = new TerraformProjectGenerator();
|
|
106
|
+
const { files } = await gen.generate(baseConfig);
|
|
107
|
+
const mainTf = files.find(f => f.path === 'main.tf');
|
|
108
|
+
|
|
109
|
+
expect(mainTf).toBeDefined();
|
|
110
|
+
expect(mainTf!.content).toContain('provider "aws"');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('generateGitignore() is callable as a standalone method', () => {
|
|
114
|
+
const gen = new TerraformProjectGenerator();
|
|
115
|
+
const file = gen.generateGitignore();
|
|
116
|
+
expect(file.path).toBe('.gitignore');
|
|
117
|
+
expect(file.content.length).toBeGreaterThan(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('validateProject() returns a ValidationReport', () => {
|
|
121
|
+
const gen = new TerraformProjectGenerator();
|
|
122
|
+
const report = gen.validateProject([
|
|
123
|
+
{ path: 'main.tf', content: 'provider "aws" {}' },
|
|
124
|
+
{ path: 'variables.tf', content: '' },
|
|
125
|
+
{ path: 'outputs.tf', content: '' },
|
|
126
|
+
{ path: 'versions.tf', content: '' },
|
|
127
|
+
{ path: 'backend.tf', content: '' },
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
expect(typeof report.valid).toBe('boolean');
|
|
131
|
+
expect(Array.isArray(report.items)).toBe(true);
|
|
132
|
+
expect(typeof report.summary.errors).toBe('number');
|
|
133
|
+
expect(typeof report.summary.warnings).toBe('number');
|
|
134
|
+
expect(typeof report.summary.info).toBe('number');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// KubernetesGenerator
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
describe('KubernetesGenerator', () => {
|
|
143
|
+
const baseK8sConfig: K8sGeneratorConfig = {
|
|
144
|
+
appName: 'my-app',
|
|
145
|
+
workloadType: 'deployment',
|
|
146
|
+
image: 'nginx',
|
|
147
|
+
imageTag: '1.25',
|
|
148
|
+
replicas: 2,
|
|
149
|
+
containerPort: 8080,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
it('can be instantiated directly', () => {
|
|
153
|
+
const gen = new KubernetesGenerator(baseK8sConfig);
|
|
154
|
+
expect(gen).toBeInstanceOf(KubernetesGenerator);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('can be created via the factory function', () => {
|
|
158
|
+
const gen = createKubernetesGenerator(baseK8sConfig);
|
|
159
|
+
expect(gen).toBeInstanceOf(KubernetesGenerator);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('generate() returns an array of GeneratedManifest objects', () => {
|
|
163
|
+
const gen = new KubernetesGenerator(baseK8sConfig);
|
|
164
|
+
const manifests = gen.generate();
|
|
165
|
+
|
|
166
|
+
expect(Array.isArray(manifests)).toBe(true);
|
|
167
|
+
expect(manifests.length).toBeGreaterThan(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('manifests contain a Deployment', () => {
|
|
171
|
+
const gen = new KubernetesGenerator(baseK8sConfig);
|
|
172
|
+
const manifests = gen.generate();
|
|
173
|
+
const deployment = manifests.find(m => m.kind === 'Deployment');
|
|
174
|
+
|
|
175
|
+
expect(deployment).toBeDefined();
|
|
176
|
+
expect(deployment!.content).toContain('kind: Deployment');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('manifests contain a Service when serviceType is not None', () => {
|
|
180
|
+
const gen = new KubernetesGenerator({ ...baseK8sConfig, serviceType: 'ClusterIP' });
|
|
181
|
+
const manifests = gen.generate();
|
|
182
|
+
const service = manifests.find(m => m.kind === 'Service');
|
|
183
|
+
|
|
184
|
+
expect(service).toBeDefined();
|
|
185
|
+
expect(service!.content).toContain('kind: Service');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('no Service manifest is generated when serviceType is None', () => {
|
|
189
|
+
const gen = new KubernetesGenerator({ ...baseK8sConfig, serviceType: 'None' });
|
|
190
|
+
const manifests = gen.generate();
|
|
191
|
+
const service = manifests.find(m => m.kind === 'Service');
|
|
192
|
+
expect(service).toBeUndefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('generateCombined() returns a YAML string with --- separators', () => {
|
|
196
|
+
const gen = new KubernetesGenerator(baseK8sConfig);
|
|
197
|
+
const combined = gen.generateCombined();
|
|
198
|
+
|
|
199
|
+
expect(typeof combined).toBe('string');
|
|
200
|
+
expect(combined.length).toBeGreaterThan(0);
|
|
201
|
+
expect(combined).toContain('---');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('Deployment manifest includes the correct image and replicas', () => {
|
|
205
|
+
const gen = new KubernetesGenerator(baseK8sConfig);
|
|
206
|
+
const manifests = gen.generate();
|
|
207
|
+
const deployment = manifests.find(m => m.kind === 'Deployment');
|
|
208
|
+
|
|
209
|
+
expect(deployment!.content).toContain('nginx:1.25');
|
|
210
|
+
expect(deployment!.content).toContain('replicas: 2');
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('generates Namespace manifest when namespace is not "default"', () => {
|
|
214
|
+
const gen = new KubernetesGenerator({ ...baseK8sConfig, namespace: 'production' });
|
|
215
|
+
const manifests = gen.generate();
|
|
216
|
+
const ns = manifests.find(m => m.kind === 'Namespace');
|
|
217
|
+
|
|
218
|
+
expect(ns).toBeDefined();
|
|
219
|
+
expect(ns!.content).toContain('name: production');
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('generates HPA manifest when hpa.enabled is true', () => {
|
|
223
|
+
const gen = new KubernetesGenerator({
|
|
224
|
+
...baseK8sConfig,
|
|
225
|
+
hpa: { enabled: true, minReplicas: 2, maxReplicas: 10 },
|
|
226
|
+
});
|
|
227
|
+
const manifests = gen.generate();
|
|
228
|
+
const hpa = manifests.find(m => m.kind === 'HorizontalPodAutoscaler');
|
|
229
|
+
|
|
230
|
+
expect(hpa).toBeDefined();
|
|
231
|
+
expect(hpa!.content).toContain('HorizontalPodAutoscaler');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// HelmGenerator
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
describe('HelmGenerator', () => {
|
|
240
|
+
const baseHelmConfig: HelmChartConfig = {
|
|
241
|
+
name: 'my-chart',
|
|
242
|
+
version: '1.0.0',
|
|
243
|
+
appVersion: '2.0.0',
|
|
244
|
+
description: 'A test Helm chart',
|
|
245
|
+
values: {
|
|
246
|
+
image: {
|
|
247
|
+
repository: 'my-org/my-app',
|
|
248
|
+
tag: 'latest',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
it('can be instantiated directly', () => {
|
|
254
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
255
|
+
expect(gen).toBeInstanceOf(HelmGenerator);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('can be created via the factory function', () => {
|
|
259
|
+
const gen = createHelmGenerator(baseHelmConfig);
|
|
260
|
+
expect(gen).toBeInstanceOf(HelmGenerator);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('generate() returns an array of GeneratedFile objects', () => {
|
|
264
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
265
|
+
const files = gen.generate();
|
|
266
|
+
|
|
267
|
+
expect(Array.isArray(files)).toBe(true);
|
|
268
|
+
expect(files.length).toBeGreaterThan(0);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('includes Chart.yaml in the generated files', () => {
|
|
272
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
273
|
+
const files = gen.generate();
|
|
274
|
+
const chart = files.find(f => f.path === 'Chart.yaml');
|
|
275
|
+
|
|
276
|
+
expect(chart).toBeDefined();
|
|
277
|
+
expect(chart!.content).toContain('name: my-chart');
|
|
278
|
+
expect(chart!.content).toContain('version: 1.0.0');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('includes values.yaml in the generated files', () => {
|
|
282
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
283
|
+
const files = gen.generate();
|
|
284
|
+
const values = files.find(f => f.path === 'values.yaml');
|
|
285
|
+
|
|
286
|
+
expect(values).toBeDefined();
|
|
287
|
+
expect(values!.content).toContain('repository: my-org/my-app');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('includes a deployment template', () => {
|
|
291
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
292
|
+
const files = gen.generate();
|
|
293
|
+
const deployment = files.find(f => f.path === 'templates/deployment.yaml');
|
|
294
|
+
|
|
295
|
+
expect(deployment).toBeDefined();
|
|
296
|
+
expect(deployment!.content).toContain('kind: Deployment');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('includes a service template', () => {
|
|
300
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
301
|
+
const files = gen.generate();
|
|
302
|
+
const service = files.find(f => f.path === 'templates/service.yaml');
|
|
303
|
+
|
|
304
|
+
expect(service).toBeDefined();
|
|
305
|
+
expect(service!.content).toContain('kind: Service');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('includes a _helpers.tpl template', () => {
|
|
309
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
310
|
+
const files = gen.generate();
|
|
311
|
+
const helpers = files.find(f => f.path === 'templates/_helpers.tpl');
|
|
312
|
+
|
|
313
|
+
expect(helpers).toBeDefined();
|
|
314
|
+
expect(helpers!.content).toContain('define "my-chart.name"');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('Chart.yaml contains the appVersion field', () => {
|
|
318
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
319
|
+
const files = gen.generate();
|
|
320
|
+
const chart = files.find(f => f.path === 'Chart.yaml');
|
|
321
|
+
|
|
322
|
+
expect(chart!.content).toContain('appVersion:');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('includes .helmignore', () => {
|
|
326
|
+
const gen = new HelmGenerator(baseHelmConfig);
|
|
327
|
+
const files = gen.generate();
|
|
328
|
+
const helmignore = files.find(f => f.path === '.helmignore');
|
|
329
|
+
|
|
330
|
+
expect(helmignore).toBeDefined();
|
|
331
|
+
expect(helmignore!.content).toContain('.git/');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// BestPracticesEngine
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
describe('BestPracticesEngine', () => {
|
|
340
|
+
it('can be instantiated with no arguments', () => {
|
|
341
|
+
const engine = new BestPracticesEngine();
|
|
342
|
+
expect(engine).toBeInstanceOf(BestPracticesEngine);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('can be instantiated with custom rules', () => {
|
|
346
|
+
const customRule = {
|
|
347
|
+
id: 'custom-001',
|
|
348
|
+
category: 'security' as const,
|
|
349
|
+
severity: 'low' as const,
|
|
350
|
+
title: 'Custom Rule',
|
|
351
|
+
description: 'A test custom rule',
|
|
352
|
+
recommendation: 'Do something',
|
|
353
|
+
applies_to: ['vpc'],
|
|
354
|
+
check: (_config: Record<string, unknown>) => true,
|
|
355
|
+
};
|
|
356
|
+
const engine = new BestPracticesEngine([customRule]);
|
|
357
|
+
expect(engine).toBeInstanceOf(BestPracticesEngine);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it('listRules() returns an array with at least one rule', () => {
|
|
361
|
+
const engine = new BestPracticesEngine();
|
|
362
|
+
const rules = engine.listRules();
|
|
363
|
+
expect(Array.isArray(rules)).toBe(true);
|
|
364
|
+
expect(rules.length).toBeGreaterThan(0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('getRule() returns the rule for a known ID', () => {
|
|
368
|
+
const engine = new BestPracticesEngine();
|
|
369
|
+
const rule = engine.getRule('sec-001');
|
|
370
|
+
expect(rule).toBeDefined();
|
|
371
|
+
expect(rule!.id).toBe('sec-001');
|
|
372
|
+
expect(rule!.category).toBe('security');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('getRule() returns undefined for an unknown rule ID', () => {
|
|
376
|
+
const engine = new BestPracticesEngine();
|
|
377
|
+
expect(engine.getRule('does-not-exist')).toBeUndefined();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('analyze() returns a BestPracticeReport with summary and violations', () => {
|
|
381
|
+
const engine = new BestPracticesEngine();
|
|
382
|
+
// An s3 config that deliberately violates encryption and public-access rules
|
|
383
|
+
const badConfig: Record<string, unknown> = {
|
|
384
|
+
storage_encrypted: false,
|
|
385
|
+
block_public_acls: false,
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const report = engine.analyze('s3', badConfig);
|
|
389
|
+
|
|
390
|
+
expect(report).toBeDefined();
|
|
391
|
+
expect(typeof report.summary.total_rules_checked).toBe('number');
|
|
392
|
+
expect(typeof report.summary.violations_found).toBe('number');
|
|
393
|
+
expect(Array.isArray(report.violations)).toBe(true);
|
|
394
|
+
expect(Array.isArray(report.recommendations)).toBe(true);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('analyze() with a fully compliant config produces fewer violations', () => {
|
|
398
|
+
const engine = new BestPracticesEngine();
|
|
399
|
+
|
|
400
|
+
const badReport = engine.analyze('s3', {});
|
|
401
|
+
const goodReport = engine.analyze('s3', {
|
|
402
|
+
storage_encrypted: true,
|
|
403
|
+
encryption_enabled: true,
|
|
404
|
+
block_public_acls: true,
|
|
405
|
+
block_public_policy: true,
|
|
406
|
+
ignore_public_acls: true,
|
|
407
|
+
restrict_public_buckets: true,
|
|
408
|
+
enable_versioning: true,
|
|
409
|
+
enable_lifecycle_rules: true,
|
|
410
|
+
abort_incomplete_multipart_days: 7,
|
|
411
|
+
enable_access_logging: true,
|
|
412
|
+
sse_algorithm: 'aws:kms',
|
|
413
|
+
tags: { Environment: 'dev', ManagedBy: 'Terraform', Project: 'test' },
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(goodReport.summary.violations_found).toBeLessThan(badReport.summary.violations_found);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('getComplianceScore() returns 100 when no rules apply', () => {
|
|
420
|
+
const engine = new BestPracticesEngine();
|
|
421
|
+
const report = engine.analyze('nonexistent-component', {});
|
|
422
|
+
const score = engine.getComplianceScore(report);
|
|
423
|
+
expect(score).toBe(100);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('formatReportAsMarkdown() returns a markdown string', () => {
|
|
427
|
+
const engine = new BestPracticesEngine();
|
|
428
|
+
const report = engine.analyze('vpc', {});
|
|
429
|
+
const md = engine.formatReportAsMarkdown(report);
|
|
430
|
+
expect(typeof md).toBe('string');
|
|
431
|
+
expect(md).toContain('# Best Practices Report');
|
|
432
|
+
});
|
|
433
|
+
});
|