@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,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Schema Tests
|
|
3
|
+
*
|
|
4
|
+
* Validates tool definitions, ToolRegistry, permission tiers, and schema
|
|
5
|
+
* structures for both standard and DevOps tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, test, expect, beforeEach } from 'bun:test';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import {
|
|
11
|
+
ToolRegistry,
|
|
12
|
+
PERMISSION_TIER_ORDER,
|
|
13
|
+
permissionTierIndex,
|
|
14
|
+
type ToolDefinition,
|
|
15
|
+
} from '../tools/schemas/types';
|
|
16
|
+
import { standardTools } from '../tools/schemas/standard';
|
|
17
|
+
import { devopsTools } from '../tools/schemas/devops';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Helpers
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/** Create a minimal valid ToolDefinition for testing. */
|
|
24
|
+
function makeTool(name: string, overrides?: Partial<ToolDefinition>): ToolDefinition {
|
|
25
|
+
return {
|
|
26
|
+
name,
|
|
27
|
+
description: `Test tool: ${name}`,
|
|
28
|
+
inputSchema: z.object({}),
|
|
29
|
+
execute: async () => ({ output: 'ok', isError: false }),
|
|
30
|
+
permissionTier: 'auto_allow',
|
|
31
|
+
category: 'standard',
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ===========================================================================
|
|
37
|
+
// ToolRegistry
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
|
|
40
|
+
describe('ToolRegistry', () => {
|
|
41
|
+
let registry: ToolRegistry;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
registry = new ToolRegistry();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('register and get a tool', () => {
|
|
48
|
+
const tool = makeTool('alpha');
|
|
49
|
+
registry.register(tool);
|
|
50
|
+
expect(registry.get('alpha')).toBe(tool);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('get returns undefined for unknown tool', () => {
|
|
54
|
+
expect(registry.get('nonexistent')).toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('getAll returns all registered tools', () => {
|
|
58
|
+
registry.register(makeTool('a'));
|
|
59
|
+
registry.register(makeTool('b'));
|
|
60
|
+
registry.register(makeTool('c'));
|
|
61
|
+
const all = registry.getAll();
|
|
62
|
+
expect(all).toHaveLength(3);
|
|
63
|
+
expect(all.map(t => t.name)).toEqual(['a', 'b', 'c']);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('getByCategory filters correctly', () => {
|
|
67
|
+
registry.register(makeTool('s1', { category: 'standard' }));
|
|
68
|
+
registry.register(makeTool('d1', { category: 'devops' }));
|
|
69
|
+
registry.register(makeTool('s2', { category: 'standard' }));
|
|
70
|
+
expect(registry.getByCategory('standard')).toHaveLength(2);
|
|
71
|
+
expect(registry.getByCategory('devops')).toHaveLength(1);
|
|
72
|
+
expect(registry.getByCategory('mcp')).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('getByPermissionTier filters correctly', () => {
|
|
76
|
+
registry.register(makeTool('t1', { permissionTier: 'auto_allow' }));
|
|
77
|
+
registry.register(makeTool('t2', { permissionTier: 'ask_once' }));
|
|
78
|
+
registry.register(makeTool('t3', { permissionTier: 'auto_allow' }));
|
|
79
|
+
expect(registry.getByPermissionTier('auto_allow')).toHaveLength(2);
|
|
80
|
+
expect(registry.getByPermissionTier('ask_once')).toHaveLength(1);
|
|
81
|
+
expect(registry.getByPermissionTier('always_ask')).toHaveLength(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('getNames returns tool names in insertion order', () => {
|
|
85
|
+
registry.register(makeTool('z'));
|
|
86
|
+
registry.register(makeTool('a'));
|
|
87
|
+
registry.register(makeTool('m'));
|
|
88
|
+
expect(registry.getNames()).toEqual(['z', 'a', 'm']);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('unregister removes a tool and returns true', () => {
|
|
92
|
+
registry.register(makeTool('x'));
|
|
93
|
+
expect(registry.unregister('x')).toBe(true);
|
|
94
|
+
expect(registry.get('x')).toBeUndefined();
|
|
95
|
+
expect(registry.size).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('unregister returns false for non-existent tool', () => {
|
|
99
|
+
expect(registry.unregister('nope')).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('clear removes all tools', () => {
|
|
103
|
+
registry.register(makeTool('a'));
|
|
104
|
+
registry.register(makeTool('b'));
|
|
105
|
+
registry.clear();
|
|
106
|
+
expect(registry.size).toBe(0);
|
|
107
|
+
expect(registry.getAll()).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('size reflects the number of registered tools', () => {
|
|
111
|
+
expect(registry.size).toBe(0);
|
|
112
|
+
registry.register(makeTool('one'));
|
|
113
|
+
expect(registry.size).toBe(1);
|
|
114
|
+
registry.register(makeTool('two'));
|
|
115
|
+
expect(registry.size).toBe(2);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('throws on duplicate registration', () => {
|
|
119
|
+
registry.register(makeTool('dup'));
|
|
120
|
+
expect(() => registry.register(makeTool('dup'))).toThrow(/already registered/);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ===========================================================================
|
|
125
|
+
// PERMISSION_TIER_ORDER & permissionTierIndex
|
|
126
|
+
// ===========================================================================
|
|
127
|
+
|
|
128
|
+
describe('PERMISSION_TIER_ORDER', () => {
|
|
129
|
+
test('is ordered from least to most restrictive', () => {
|
|
130
|
+
expect(PERMISSION_TIER_ORDER).toEqual(['auto_allow', 'ask_once', 'always_ask', 'blocked']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('has exactly 4 tiers', () => {
|
|
134
|
+
expect(PERMISSION_TIER_ORDER).toHaveLength(4);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('permissionTierIndex', () => {
|
|
139
|
+
test('returns 0 for auto_allow', () => {
|
|
140
|
+
expect(permissionTierIndex('auto_allow')).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('returns 1 for ask_once', () => {
|
|
144
|
+
expect(permissionTierIndex('ask_once')).toBe(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('returns 2 for always_ask', () => {
|
|
148
|
+
expect(permissionTierIndex('always_ask')).toBe(2);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('returns 3 for blocked', () => {
|
|
152
|
+
expect(permissionTierIndex('blocked')).toBe(3);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('indices are strictly ascending', () => {
|
|
156
|
+
const indices = PERMISSION_TIER_ORDER.map(permissionTierIndex);
|
|
157
|
+
for (let i = 1; i < indices.length; i++) {
|
|
158
|
+
expect(indices[i]).toBeGreaterThan(indices[i - 1]);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ===========================================================================
|
|
164
|
+
// Standard Tool Schema Validation
|
|
165
|
+
// ===========================================================================
|
|
166
|
+
|
|
167
|
+
describe('Standard tool schemas', () => {
|
|
168
|
+
/** Helper to find a standard tool by name. */
|
|
169
|
+
function findStandard(name: string): ToolDefinition {
|
|
170
|
+
const t = standardTools.find(t => t.name === name);
|
|
171
|
+
if (!t) {
|
|
172
|
+
throw new Error(`Standard tool '${name}' not found`);
|
|
173
|
+
}
|
|
174
|
+
return t;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
test('read_file: path is required, offset/limit are optional numbers', () => {
|
|
178
|
+
const schema = findStandard('read_file').inputSchema;
|
|
179
|
+
// Valid with just path
|
|
180
|
+
expect(() => schema.parse({ path: '/tmp/f.txt' })).not.toThrow();
|
|
181
|
+
// Valid with offset and limit
|
|
182
|
+
expect(() => schema.parse({ path: '/tmp/f.txt', offset: 5, limit: 10 })).not.toThrow();
|
|
183
|
+
// Missing path -> error
|
|
184
|
+
expect(() => schema.parse({})).toThrow();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('edit_file: path, old_string, new_string are all required', () => {
|
|
188
|
+
const schema = findStandard('edit_file').inputSchema;
|
|
189
|
+
expect(() => schema.parse({ path: 'f', old_string: 'a', new_string: 'b' })).not.toThrow();
|
|
190
|
+
expect(() => schema.parse({ path: 'f', old_string: 'a' })).toThrow();
|
|
191
|
+
expect(() => schema.parse({ path: 'f' })).toThrow();
|
|
192
|
+
expect(() => schema.parse({})).toThrow();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test('multi_edit: edits array structure is validated', () => {
|
|
196
|
+
const schema = findStandard('multi_edit').inputSchema;
|
|
197
|
+
expect(() =>
|
|
198
|
+
schema.parse({
|
|
199
|
+
path: 'f',
|
|
200
|
+
edits: [{ old_string: 'a', new_string: 'b' }],
|
|
201
|
+
})
|
|
202
|
+
).not.toThrow();
|
|
203
|
+
// edits missing -> error
|
|
204
|
+
expect(() => schema.parse({ path: 'f' })).toThrow();
|
|
205
|
+
// empty edits array is valid
|
|
206
|
+
expect(() => schema.parse({ path: 'f', edits: [] })).not.toThrow();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('write_file: path and content are required', () => {
|
|
210
|
+
const schema = findStandard('write_file').inputSchema;
|
|
211
|
+
expect(() => schema.parse({ path: '/tmp/x', content: 'hello' })).not.toThrow();
|
|
212
|
+
expect(() => schema.parse({ path: '/tmp/x' })).toThrow();
|
|
213
|
+
expect(() => schema.parse({ content: 'hello' })).toThrow();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('bash: command is required, timeout defaults to 120000', () => {
|
|
217
|
+
const schema = findStandard('bash').inputSchema;
|
|
218
|
+
const result = schema.parse({ command: 'echo hi' }) as { command: string; timeout: number };
|
|
219
|
+
expect(result.command).toBe('echo hi');
|
|
220
|
+
expect(result.timeout).toBe(120_000);
|
|
221
|
+
expect(() => schema.parse({})).toThrow();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('glob: pattern is required', () => {
|
|
225
|
+
const schema = findStandard('glob').inputSchema;
|
|
226
|
+
expect(() => schema.parse({ pattern: '**/*.ts' })).not.toThrow();
|
|
227
|
+
expect(() => schema.parse({})).toThrow();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('grep: pattern is required', () => {
|
|
231
|
+
const schema = findStandard('grep').inputSchema;
|
|
232
|
+
expect(() => schema.parse({ pattern: 'TODO' })).not.toThrow();
|
|
233
|
+
expect(() => schema.parse({})).toThrow();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('list_dir: path is required', () => {
|
|
237
|
+
const schema = findStandard('list_dir').inputSchema;
|
|
238
|
+
expect(() => schema.parse({ path: '/tmp' })).not.toThrow();
|
|
239
|
+
expect(() => schema.parse({})).toThrow();
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('webfetch: url is required and must be valid URL', () => {
|
|
243
|
+
const schema = findStandard('webfetch').inputSchema;
|
|
244
|
+
expect(() => schema.parse({ url: 'https://example.com' })).not.toThrow();
|
|
245
|
+
expect(() => schema.parse({ url: 'not-a-url' })).toThrow();
|
|
246
|
+
expect(() => schema.parse({})).toThrow();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test('todo_read: accepts empty input', () => {
|
|
250
|
+
const schema = findStandard('todo_read').inputSchema;
|
|
251
|
+
expect(() => schema.parse({})).not.toThrow();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('todo_write: validates tasks array with subject and status enum', () => {
|
|
255
|
+
const schema = findStandard('todo_write').inputSchema;
|
|
256
|
+
expect(() =>
|
|
257
|
+
schema.parse({
|
|
258
|
+
tasks: [{ subject: 'Fix bug', status: 'pending' }],
|
|
259
|
+
})
|
|
260
|
+
).not.toThrow();
|
|
261
|
+
// Invalid status
|
|
262
|
+
expect(() =>
|
|
263
|
+
schema.parse({
|
|
264
|
+
tasks: [{ subject: 'X', status: 'invalid_status' }],
|
|
265
|
+
})
|
|
266
|
+
).toThrow();
|
|
267
|
+
// Missing tasks
|
|
268
|
+
expect(() => schema.parse({})).toThrow();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ===========================================================================
|
|
273
|
+
// DevOps Tool Schema Validation
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
|
|
276
|
+
describe('DevOps tool schemas', () => {
|
|
277
|
+
/** Helper to find a devops tool by name. */
|
|
278
|
+
function findDevops(name: string): ToolDefinition {
|
|
279
|
+
const t = devopsTools.find(t => t.name === name);
|
|
280
|
+
if (!t) {
|
|
281
|
+
throw new Error(`DevOps tool '${name}' not found`);
|
|
282
|
+
}
|
|
283
|
+
return t;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
test('terraform: action enum and workdir are required', () => {
|
|
287
|
+
const schema = findDevops('terraform').inputSchema;
|
|
288
|
+
expect(() => schema.parse({ action: 'plan', workdir: '/infra' })).not.toThrow();
|
|
289
|
+
expect(() => schema.parse({ action: 'invalid', workdir: '/infra' })).toThrow();
|
|
290
|
+
expect(() => schema.parse({ action: 'plan' })).toThrow();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('kubectl: action enum is validated', () => {
|
|
294
|
+
const schema = findDevops('kubectl').inputSchema;
|
|
295
|
+
expect(() => schema.parse({ action: 'get' })).not.toThrow();
|
|
296
|
+
expect(() => schema.parse({ action: 'invalid_action' })).toThrow();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('helm: action enum is validated', () => {
|
|
300
|
+
const schema = findDevops('helm').inputSchema;
|
|
301
|
+
expect(() => schema.parse({ action: 'list' })).not.toThrow();
|
|
302
|
+
expect(() => schema.parse({ action: 'invalid_action' })).toThrow();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test('cloud_discover: provider enum and resource_type are required', () => {
|
|
306
|
+
const schema = findDevops('cloud_discover').inputSchema;
|
|
307
|
+
expect(() => schema.parse({ provider: 'aws', resource_type: 'ec2' })).not.toThrow();
|
|
308
|
+
expect(() => schema.parse({ provider: 'invalid', resource_type: 'ec2' })).toThrow();
|
|
309
|
+
expect(() => schema.parse({ provider: 'aws' })).toThrow();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test('cost_estimate: plan_file and workdir are both optional', () => {
|
|
313
|
+
const schema = findDevops('cost_estimate').inputSchema;
|
|
314
|
+
expect(() => schema.parse({})).not.toThrow();
|
|
315
|
+
expect(() => schema.parse({ plan_file: '/plan.tfplan' })).not.toThrow();
|
|
316
|
+
expect(() => schema.parse({ workdir: '/infra' })).not.toThrow();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('drift_detect: workdir is required', () => {
|
|
320
|
+
const schema = findDevops('drift_detect').inputSchema;
|
|
321
|
+
expect(() => schema.parse({ workdir: '/infra' })).not.toThrow();
|
|
322
|
+
expect(() => schema.parse({})).toThrow();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('deploy_preview: action and workdir are required', () => {
|
|
326
|
+
const schema = findDevops('deploy_preview').inputSchema;
|
|
327
|
+
expect(() => schema.parse({ action: 'terraform apply', workdir: '/infra' })).not.toThrow();
|
|
328
|
+
expect(() => schema.parse({ action: 'terraform apply' })).toThrow();
|
|
329
|
+
expect(() => schema.parse({ workdir: '/infra' })).toThrow();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('git: action enum is validated', () => {
|
|
333
|
+
const schema = findDevops('git').inputSchema;
|
|
334
|
+
expect(() => schema.parse({ action: 'status' })).not.toThrow();
|
|
335
|
+
expect(() => schema.parse({ action: 'bad_action' })).toThrow();
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('task: prompt is required, agent enum is optional', () => {
|
|
339
|
+
const schema = findDevops('task').inputSchema;
|
|
340
|
+
expect(() => schema.parse({ prompt: 'do something' })).not.toThrow();
|
|
341
|
+
expect(() => schema.parse({ prompt: 'x', agent: 'explore' })).not.toThrow();
|
|
342
|
+
expect(() => schema.parse({ prompt: 'x', agent: 'invalid' })).toThrow();
|
|
343
|
+
expect(() => schema.parse({})).toThrow();
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
// ===========================================================================
|
|
348
|
+
// Tool Counts and Metadata
|
|
349
|
+
// ===========================================================================
|
|
350
|
+
|
|
351
|
+
describe('Tool counts and metadata', () => {
|
|
352
|
+
test('standardTools has exactly 12 tools', () => {
|
|
353
|
+
expect(standardTools).toHaveLength(12);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('devopsTools has exactly 9 tools', () => {
|
|
357
|
+
expect(devopsTools).toHaveLength(9);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('all standard tools have category "standard"', () => {
|
|
361
|
+
for (const tool of standardTools) {
|
|
362
|
+
expect(tool.category).toBe('standard');
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test('all devops tools have category "devops"', () => {
|
|
367
|
+
for (const tool of devopsTools) {
|
|
368
|
+
expect(tool.category).toBe('devops');
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('all tools have valid permissionTier values', () => {
|
|
373
|
+
const validTiers = new Set<string>(PERMISSION_TIER_ORDER);
|
|
374
|
+
for (const tool of [...standardTools, ...devopsTools]) {
|
|
375
|
+
expect(validTiers.has(tool.permissionTier)).toBe(true);
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('all tools have non-empty name and description', () => {
|
|
380
|
+
for (const tool of [...standardTools, ...devopsTools]) {
|
|
381
|
+
expect(tool.name.length).toBeGreaterThan(0);
|
|
382
|
+
expect(tool.description.length).toBeGreaterThan(0);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('all tool names are unique across standard and devops', () => {
|
|
387
|
+
const allNames = [...standardTools, ...devopsTools].map(t => t.name);
|
|
388
|
+
const uniqueNames = new Set(allNames);
|
|
389
|
+
expect(uniqueNames.size).toBe(allNames.length);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('all tools have an execute function', () => {
|
|
393
|
+
for (const tool of [...standardTools, ...devopsTools]) {
|
|
394
|
+
expect(typeof tool.execute).toBe('function');
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for embedded tool operation classes:
|
|
3
|
+
* - src/tools/file-ops.ts – FileSystemOperations
|
|
4
|
+
* - src/tools/git-ops.ts – GitOperations
|
|
5
|
+
*
|
|
6
|
+
* These tests verify that the classes can be instantiated and that their
|
|
7
|
+
* fundamental read/exists operations work against the real filesystem.
|
|
8
|
+
* Git network operations (clone, push, pull) are NOT exercised here to keep
|
|
9
|
+
* tests fast and hermetic.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect } from 'bun:test';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { FileSystemOperations } from '../tools/file-ops';
|
|
15
|
+
import { GitOperations } from '../tools/git-ops';
|
|
16
|
+
|
|
17
|
+
// The repository root is two levels above this test file: src/__tests__/ -> src/ -> repo-root
|
|
18
|
+
const REPO_ROOT = join(import.meta.dir, '..', '..');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// FileSystemOperations
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe('FileSystemOperations', () => {
|
|
25
|
+
it('can be instantiated with no arguments (defaults to cwd)', () => {
|
|
26
|
+
const ops = new FileSystemOperations();
|
|
27
|
+
expect(ops).toBeInstanceOf(FileSystemOperations);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('can be instantiated with a specific base path', () => {
|
|
31
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
32
|
+
expect(ops).toBeInstanceOf(FileSystemOperations);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('readFile reads the package.json at the repo root', async () => {
|
|
36
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
37
|
+
const content = await ops.readFile('package.json');
|
|
38
|
+
expect(typeof content).toBe('string');
|
|
39
|
+
expect(content.length).toBeGreaterThan(0);
|
|
40
|
+
// Verify it is valid JSON containing expected nimbus fields
|
|
41
|
+
const pkg = JSON.parse(content);
|
|
42
|
+
expect(pkg.name).toBe('@build-astron-co/nimbus');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('exists() returns true for a known file', async () => {
|
|
46
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
47
|
+
const result = await ops.exists('package.json');
|
|
48
|
+
expect(result).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('exists() returns false for a non-existent path', async () => {
|
|
52
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
53
|
+
const result = await ops.exists('this-file-does-not-exist-abc123.txt');
|
|
54
|
+
expect(result).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('stat() returns file statistics for package.json', async () => {
|
|
58
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
59
|
+
const stats = await ops.stat('package.json');
|
|
60
|
+
|
|
61
|
+
expect(stats.isFile).toBe(true);
|
|
62
|
+
expect(stats.isDirectory).toBe(false);
|
|
63
|
+
expect(stats.size).toBeGreaterThan(0);
|
|
64
|
+
expect(stats.modifiedAt).toBeInstanceOf(Date);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('readDir() lists the src directory', async () => {
|
|
68
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
69
|
+
const entries = await ops.readDir('src');
|
|
70
|
+
|
|
71
|
+
expect(Array.isArray(entries)).toBe(true);
|
|
72
|
+
expect(entries.length).toBeGreaterThan(0);
|
|
73
|
+
// src/ contains at least app.ts and version.ts
|
|
74
|
+
const names = entries.map(e => e.name);
|
|
75
|
+
expect(names).toContain('app.ts');
|
|
76
|
+
expect(names).toContain('version.ts');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('readFile throws when attempting to read a sensitive file', async () => {
|
|
80
|
+
const ops = new FileSystemOperations(REPO_ROOT);
|
|
81
|
+
// .env files are blocked by the sensitive-file guard
|
|
82
|
+
await expect(ops.readFile('.env')).rejects.toThrow();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// GitOperations
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
describe('GitOperations', () => {
|
|
91
|
+
it('can be instantiated with no arguments (defaults to cwd)', () => {
|
|
92
|
+
const git = new GitOperations();
|
|
93
|
+
expect(git).toBeInstanceOf(GitOperations);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('can be instantiated with a specific repo path', () => {
|
|
97
|
+
const git = new GitOperations(REPO_ROOT);
|
|
98
|
+
expect(git).toBeInstanceOf(GitOperations);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('isRepo() returns true for the nimbus repository', async () => {
|
|
102
|
+
const git = new GitOperations(REPO_ROOT);
|
|
103
|
+
const result = await git.isRepo();
|
|
104
|
+
expect(result).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('currentBranch() returns a non-empty string', async () => {
|
|
108
|
+
const git = new GitOperations(REPO_ROOT);
|
|
109
|
+
const branch = await git.currentBranch();
|
|
110
|
+
expect(typeof branch).toBe('string');
|
|
111
|
+
expect(branch.length).toBeGreaterThan(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('status() returns a status result with expected shape', async () => {
|
|
115
|
+
const git = new GitOperations(REPO_ROOT);
|
|
116
|
+
const status = await git.status();
|
|
117
|
+
// StatusResult from simple-git has a `current` property and isClean()
|
|
118
|
+
expect(typeof status.isClean).toBe('function');
|
|
119
|
+
expect(typeof status.current).toBe('string');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('listBranches() returns current branch and branch list', async () => {
|
|
123
|
+
const git = new GitOperations(REPO_ROOT);
|
|
124
|
+
const { current, branches } = await git.listBranches();
|
|
125
|
+
expect(typeof current).toBe('string');
|
|
126
|
+
expect(Array.isArray(branches)).toBe(true);
|
|
127
|
+
expect(branches.length).toBeGreaterThan(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('listTags() returns an array', async () => {
|
|
131
|
+
const git = new GitOperations(REPO_ROOT);
|
|
132
|
+
const tags = await git.listTags();
|
|
133
|
+
expect(Array.isArray(tags)).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('getShortHash() returns a short commit hash string', async () => {
|
|
137
|
+
const git = new GitOperations(REPO_ROOT);
|
|
138
|
+
const hash = await git.getShortHash('HEAD');
|
|
139
|
+
expect(typeof hash).toBe('string');
|
|
140
|
+
// Short hashes are typically 7–12 hex characters
|
|
141
|
+
expect(/^[0-9a-f]{4,12}$/.test(hash)).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/version.ts
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the module exports a valid semver VERSION string and a
|
|
5
|
+
* non-empty BUILD_DATE string.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from 'bun:test';
|
|
9
|
+
import { VERSION, BUILD_DATE } from '../version';
|
|
10
|
+
|
|
11
|
+
describe('version', () => {
|
|
12
|
+
it('exports VERSION as a non-empty string', () => {
|
|
13
|
+
expect(typeof VERSION).toBe('string');
|
|
14
|
+
expect(VERSION.length).toBeGreaterThan(0);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('VERSION matches semver format (MAJOR.MINOR.PATCH)', () => {
|
|
18
|
+
// A loose semver pattern: digits separated by dots, with optional pre-release
|
|
19
|
+
const semverLoose = /^\d+\.\d+\.\d+/;
|
|
20
|
+
expect(semverLoose.test(VERSION)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('VERSION major version is a non-negative integer', () => {
|
|
24
|
+
const major = parseInt(VERSION.split('.')[0], 10);
|
|
25
|
+
expect(Number.isInteger(major)).toBe(true);
|
|
26
|
+
expect(major).toBeGreaterThanOrEqual(0);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('VERSION minor and patch are non-negative integers', () => {
|
|
30
|
+
const parts = VERSION.split('.');
|
|
31
|
+
const minor = parseInt(parts[1], 10);
|
|
32
|
+
const patch = parseInt(parts[2], 10);
|
|
33
|
+
expect(minor).toBeGreaterThanOrEqual(0);
|
|
34
|
+
expect(patch).toBeGreaterThanOrEqual(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('exports BUILD_DATE as a non-empty string', () => {
|
|
38
|
+
expect(typeof BUILD_DATE).toBe('string');
|
|
39
|
+
expect(BUILD_DATE.length).toBeGreaterThan(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('BUILD_DATE is "dev" in development or ISO-like date in builds', () => {
|
|
43
|
+
// In dev mode the placeholder is detected and replaced with 'dev'.
|
|
44
|
+
// After a compiled build it is an ISO 8601 date string (YYYY-MM-DD).
|
|
45
|
+
const isDev = BUILD_DATE === 'dev';
|
|
46
|
+
const isISODate = /^\d{4}-\d{2}-\d{2}/.test(BUILD_DATE);
|
|
47
|
+
expect(isDev || isISODate).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|