vaspera 2.5.0 → 2.8.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/CHANGELOG.md +62 -0
- package/dist/agents/adversary/config.d.ts +92 -0
- package/dist/agents/adversary/config.d.ts.map +1 -0
- package/dist/agents/adversary/config.js +361 -0
- package/dist/agents/adversary/config.js.map +1 -0
- package/dist/agents/adversary/index.d.ts +34 -0
- package/dist/agents/adversary/index.d.ts.map +1 -0
- package/dist/agents/adversary/index.js +756 -0
- package/dist/agents/adversary/index.js.map +1 -0
- package/dist/agents/adversary/types.d.ts +351 -0
- package/dist/agents/adversary/types.d.ts.map +1 -0
- package/dist/agents/adversary/types.js +12 -0
- package/dist/agents/adversary/types.js.map +1 -0
- package/dist/agents/agent-integrity.test.d.ts +5 -0
- package/dist/agents/agent-integrity.test.d.ts.map +1 -0
- package/dist/agents/agent-integrity.test.js +364 -0
- package/dist/agents/agent-integrity.test.js.map +1 -0
- package/dist/agents/agent-privacy.test.d.ts +5 -0
- package/dist/agents/agent-privacy.test.d.ts.map +1 -0
- package/dist/agents/agent-privacy.test.js +373 -0
- package/dist/agents/agent-privacy.test.js.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/certification/consensus.test.js +2 -0
- package/dist/certification/consensus.test.js.map +1 -1
- package/dist/certification/store.d.ts.map +1 -1
- package/dist/certification/store.js +4 -0
- package/dist/certification/store.js.map +1 -1
- package/dist/certification/types.d.ts +2 -2
- package/dist/certification/types.d.ts.map +1 -1
- package/dist/certification/types.js +2 -0
- package/dist/certification/types.js.map +1 -1
- package/dist/compliance/mapper.d.ts.map +1 -1
- package/dist/compliance/mapper.js +2 -2
- package/dist/compliance/mapper.js.map +1 -1
- package/dist/compliance/nist-800-53.d.ts +34 -0
- package/dist/compliance/nist-800-53.d.ts.map +1 -0
- package/dist/compliance/nist-800-53.js +664 -0
- package/dist/compliance/nist-800-53.js.map +1 -0
- package/dist/config/flags.test.d.ts +5 -0
- package/dist/config/flags.test.d.ts.map +1 -0
- package/dist/config/flags.test.js +489 -0
- package/dist/config/flags.test.js.map +1 -0
- package/dist/enterprise/policy/opa.test.js +4 -1
- package/dist/enterprise/policy/opa.test.js.map +1 -1
- package/dist/http-server.js +2 -1
- package/dist/http-server.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/observability/otel.test.d.ts +5 -0
- package/dist/observability/otel.test.d.ts.map +1 -0
- package/dist/observability/otel.test.js +269 -0
- package/dist/observability/otel.test.js.map +1 -0
- package/dist/plugins/loader.test.d.ts +5 -0
- package/dist/plugins/loader.test.d.ts.map +1 -0
- package/dist/plugins/loader.test.js +337 -0
- package/dist/plugins/loader.test.js.map +1 -0
- package/dist/sbom/provenance.test.js +2 -2
- package/dist/sbom/provenance.test.js.map +1 -1
- package/dist/scanners/agent/manifest-audit.d.ts.map +1 -1
- package/dist/scanners/agent/manifest-audit.js +30 -18
- package/dist/scanners/agent/manifest-audit.js.map +1 -1
- package/dist/scanners/dependencies.d.ts.map +1 -1
- package/dist/scanners/dependencies.js +1 -2
- package/dist/scanners/dependencies.js.map +1 -1
- package/package.json +12 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.test.d.ts","sourceRoot":"","sources":["../../src/observability/otel.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for otel module
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import { isOtelEnabled, getOtlpEndpoint, initializeOtel, getMetrics, getTracer, getInMemoryStats, resetInMemoryStats, } from "./otel.js";
|
|
6
|
+
describe("isOtelEnabled", () => {
|
|
7
|
+
const originalEnv = process.env.VASPERA_OTEL_ENABLED;
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
if (originalEnv === undefined) {
|
|
10
|
+
delete process.env.VASPERA_OTEL_ENABLED;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
process.env.VASPERA_OTEL_ENABLED = originalEnv;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
it("returns false when env var is not set", () => {
|
|
17
|
+
delete process.env.VASPERA_OTEL_ENABLED;
|
|
18
|
+
expect(isOtelEnabled()).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
it("returns false when env var is 'false'", () => {
|
|
21
|
+
process.env.VASPERA_OTEL_ENABLED = "false";
|
|
22
|
+
expect(isOtelEnabled()).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
it("returns true when env var is 'true'", () => {
|
|
25
|
+
process.env.VASPERA_OTEL_ENABLED = "true";
|
|
26
|
+
expect(isOtelEnabled()).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it("returns false for any other value", () => {
|
|
29
|
+
process.env.VASPERA_OTEL_ENABLED = "1";
|
|
30
|
+
expect(isOtelEnabled()).toBe(false);
|
|
31
|
+
process.env.VASPERA_OTEL_ENABLED = "yes";
|
|
32
|
+
expect(isOtelEnabled()).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe("getOtlpEndpoint", () => {
|
|
36
|
+
const originalEnv = process.env.VASPERA_OTEL_ENDPOINT;
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
if (originalEnv === undefined) {
|
|
39
|
+
delete process.env.VASPERA_OTEL_ENDPOINT;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
process.env.VASPERA_OTEL_ENDPOINT = originalEnv;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
it("returns default endpoint when env var is not set", () => {
|
|
46
|
+
delete process.env.VASPERA_OTEL_ENDPOINT;
|
|
47
|
+
expect(getOtlpEndpoint()).toBe("http://localhost:4318");
|
|
48
|
+
});
|
|
49
|
+
it("returns custom endpoint when env var is set", () => {
|
|
50
|
+
process.env.VASPERA_OTEL_ENDPOINT = "http://otel.example.com:4317";
|
|
51
|
+
expect(getOtlpEndpoint()).toBe("http://otel.example.com:4317");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe("initializeOtel", () => {
|
|
55
|
+
const originalEnabled = process.env.VASPERA_OTEL_ENABLED;
|
|
56
|
+
const originalDebug = process.env.VASPERA_OTEL_DEBUG;
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
if (originalEnabled === undefined) {
|
|
59
|
+
delete process.env.VASPERA_OTEL_ENABLED;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
process.env.VASPERA_OTEL_ENABLED = originalEnabled;
|
|
63
|
+
}
|
|
64
|
+
if (originalDebug === undefined) {
|
|
65
|
+
delete process.env.VASPERA_OTEL_DEBUG;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
process.env.VASPERA_OTEL_DEBUG = originalDebug;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
it("does nothing when OTEL is disabled", async () => {
|
|
72
|
+
delete process.env.VASPERA_OTEL_ENABLED;
|
|
73
|
+
// Should not throw
|
|
74
|
+
await expect(initializeOtel()).resolves.toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
it("handles missing OTEL packages gracefully", async () => {
|
|
77
|
+
process.env.VASPERA_OTEL_ENABLED = "true";
|
|
78
|
+
// Should not throw even when packages are missing
|
|
79
|
+
await expect(initializeOtel()).resolves.toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("getMetrics", () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
resetInMemoryStats();
|
|
85
|
+
});
|
|
86
|
+
it("returns a VasperaMetrics object", () => {
|
|
87
|
+
const metrics = getMetrics();
|
|
88
|
+
expect(metrics).toHaveProperty("findingsTotal");
|
|
89
|
+
expect(metrics).toHaveProperty("scanDuration");
|
|
90
|
+
expect(metrics).toHaveProperty("certificationDuration");
|
|
91
|
+
expect(metrics).toHaveProperty("agentRuns");
|
|
92
|
+
expect(metrics).toHaveProperty("circuitBreakerState");
|
|
93
|
+
expect(metrics).toHaveProperty("retryAttempts");
|
|
94
|
+
});
|
|
95
|
+
it("findingsTotal increments in-memory counter", () => {
|
|
96
|
+
const metrics = getMetrics();
|
|
97
|
+
metrics.findingsTotal("high", "semgrep");
|
|
98
|
+
metrics.findingsTotal("high", "semgrep");
|
|
99
|
+
metrics.findingsTotal("medium", "semgrep");
|
|
100
|
+
const stats = getInMemoryStats();
|
|
101
|
+
expect(stats.findings["semgrep:high"]).toBe(2);
|
|
102
|
+
expect(stats.findings["semgrep:medium"]).toBe(1);
|
|
103
|
+
});
|
|
104
|
+
it("scanDuration records scan durations", () => {
|
|
105
|
+
const metrics = getMetrics();
|
|
106
|
+
metrics.scanDuration("semgrep", 1000);
|
|
107
|
+
metrics.scanDuration("gitleaks", 500);
|
|
108
|
+
const stats = getInMemoryStats();
|
|
109
|
+
expect(stats.recentScans.length).toBe(2);
|
|
110
|
+
expect(stats.recentScans[0].scanner).toBe("semgrep");
|
|
111
|
+
expect(stats.recentScans[0].durationMs).toBe(1000);
|
|
112
|
+
expect(stats.recentScans[1].scanner).toBe("gitleaks");
|
|
113
|
+
expect(stats.recentScans[1].durationMs).toBe(500);
|
|
114
|
+
});
|
|
115
|
+
it("agentRuns tracks success/failure by agent type", () => {
|
|
116
|
+
const metrics = getMetrics();
|
|
117
|
+
metrics.agentRuns("security", "success");
|
|
118
|
+
metrics.agentRuns("security", "success");
|
|
119
|
+
metrics.agentRuns("security", "failure");
|
|
120
|
+
metrics.agentRuns("reliability", "success");
|
|
121
|
+
const stats = getInMemoryStats();
|
|
122
|
+
expect(stats.agentRuns["security"]).toEqual({ success: 2, failure: 1 });
|
|
123
|
+
expect(stats.agentRuns["reliability"]).toEqual({ success: 1, failure: 0 });
|
|
124
|
+
});
|
|
125
|
+
it("certificationDuration is callable (no-op)", () => {
|
|
126
|
+
const metrics = getMetrics();
|
|
127
|
+
// Should not throw
|
|
128
|
+
expect(() => metrics.certificationDuration(5000)).not.toThrow();
|
|
129
|
+
});
|
|
130
|
+
it("circuitBreakerState is callable (no-op)", () => {
|
|
131
|
+
const metrics = getMetrics();
|
|
132
|
+
// Should not throw
|
|
133
|
+
expect(() => metrics.circuitBreakerState("api-client", "open")).not.toThrow();
|
|
134
|
+
});
|
|
135
|
+
it("retryAttempts is callable (no-op)", () => {
|
|
136
|
+
const metrics = getMetrics();
|
|
137
|
+
// Should not throw
|
|
138
|
+
expect(() => metrics.retryAttempts("fetch", 3)).not.toThrow();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe("getTracer", () => {
|
|
142
|
+
it("returns a Tracer object", () => {
|
|
143
|
+
const tracer = getTracer();
|
|
144
|
+
expect(tracer).toHaveProperty("startSpan");
|
|
145
|
+
expect(tracer).toHaveProperty("withSpan");
|
|
146
|
+
});
|
|
147
|
+
it("startSpan returns a Span object", () => {
|
|
148
|
+
const tracer = getTracer();
|
|
149
|
+
const span = tracer.startSpan("test-span");
|
|
150
|
+
expect(span).toHaveProperty("setAttribute");
|
|
151
|
+
expect(span).toHaveProperty("recordException");
|
|
152
|
+
expect(span).toHaveProperty("end");
|
|
153
|
+
expect(span).toHaveProperty("getContext");
|
|
154
|
+
});
|
|
155
|
+
it("span has valid context with traceId and spanId", () => {
|
|
156
|
+
const tracer = getTracer();
|
|
157
|
+
const span = tracer.startSpan("test-span");
|
|
158
|
+
const context = span.getContext();
|
|
159
|
+
expect(context.traceId).toBeDefined();
|
|
160
|
+
expect(context.spanId).toBeDefined();
|
|
161
|
+
expect(context.traceId).toHaveLength(32);
|
|
162
|
+
expect(context.spanId).toHaveLength(16);
|
|
163
|
+
});
|
|
164
|
+
it("span methods do not throw", () => {
|
|
165
|
+
const tracer = getTracer();
|
|
166
|
+
const span = tracer.startSpan("test-span");
|
|
167
|
+
expect(() => span.setAttribute("key", "value")).not.toThrow();
|
|
168
|
+
expect(() => span.setAttribute("count", 42)).not.toThrow();
|
|
169
|
+
expect(() => span.setAttribute("enabled", true)).not.toThrow();
|
|
170
|
+
expect(() => span.recordException(new Error("test error"))).not.toThrow();
|
|
171
|
+
expect(() => span.end()).not.toThrow();
|
|
172
|
+
});
|
|
173
|
+
it("withSpan executes the callback and returns result", async () => {
|
|
174
|
+
const tracer = getTracer();
|
|
175
|
+
const result = await tracer.withSpan("test-operation", async (span) => {
|
|
176
|
+
span.setAttribute("step", "processing");
|
|
177
|
+
return "success";
|
|
178
|
+
});
|
|
179
|
+
expect(result).toBe("success");
|
|
180
|
+
});
|
|
181
|
+
it("withSpan handles errors in callback", async () => {
|
|
182
|
+
const tracer = getTracer();
|
|
183
|
+
await expect(tracer.withSpan("failing-operation", async () => {
|
|
184
|
+
throw new Error("operation failed");
|
|
185
|
+
})).rejects.toThrow("operation failed");
|
|
186
|
+
});
|
|
187
|
+
it("withSpan provides span to callback", async () => {
|
|
188
|
+
const tracer = getTracer();
|
|
189
|
+
let capturedSpan;
|
|
190
|
+
await tracer.withSpan("capture-span", async (span) => {
|
|
191
|
+
capturedSpan = span;
|
|
192
|
+
});
|
|
193
|
+
expect(capturedSpan).toBeDefined();
|
|
194
|
+
expect(capturedSpan).toHaveProperty("setAttribute");
|
|
195
|
+
expect(capturedSpan).toHaveProperty("getContext");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe("getInMemoryStats", () => {
|
|
199
|
+
beforeEach(() => {
|
|
200
|
+
resetInMemoryStats();
|
|
201
|
+
});
|
|
202
|
+
it("returns empty stats initially", () => {
|
|
203
|
+
const stats = getInMemoryStats();
|
|
204
|
+
expect(stats.findings).toEqual({});
|
|
205
|
+
expect(stats.recentScans).toEqual([]);
|
|
206
|
+
expect(stats.agentRuns).toEqual({});
|
|
207
|
+
});
|
|
208
|
+
it("reflects metrics after recording", () => {
|
|
209
|
+
const metrics = getMetrics();
|
|
210
|
+
metrics.findingsTotal("critical", "trivy");
|
|
211
|
+
metrics.scanDuration("trivy", 2000);
|
|
212
|
+
metrics.agentRuns("redteam", "success");
|
|
213
|
+
const stats = getInMemoryStats();
|
|
214
|
+
expect(stats.findings["trivy:critical"]).toBe(1);
|
|
215
|
+
expect(stats.recentScans.length).toBe(1);
|
|
216
|
+
expect(stats.agentRuns["redteam"]).toEqual({ success: 1, failure: 0 });
|
|
217
|
+
});
|
|
218
|
+
it("returns only last 100 scans in recentScans", () => {
|
|
219
|
+
const metrics = getMetrics();
|
|
220
|
+
// Record 150 scans
|
|
221
|
+
for (let i = 0; i < 150; i++) {
|
|
222
|
+
metrics.scanDuration("scanner", i);
|
|
223
|
+
}
|
|
224
|
+
const stats = getInMemoryStats();
|
|
225
|
+
// Should only return last 100
|
|
226
|
+
expect(stats.recentScans.length).toBeLessThanOrEqual(100);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
describe("resetInMemoryStats", () => {
|
|
230
|
+
it("clears all in-memory metrics", () => {
|
|
231
|
+
const metrics = getMetrics();
|
|
232
|
+
metrics.findingsTotal("high", "semgrep");
|
|
233
|
+
metrics.scanDuration("semgrep", 1000);
|
|
234
|
+
metrics.agentRuns("security", "success");
|
|
235
|
+
resetInMemoryStats();
|
|
236
|
+
const stats = getInMemoryStats();
|
|
237
|
+
expect(stats.findings).toEqual({});
|
|
238
|
+
expect(stats.recentScans).toEqual([]);
|
|
239
|
+
expect(stats.agentRuns).toEqual({});
|
|
240
|
+
});
|
|
241
|
+
it("allows recording new metrics after reset", () => {
|
|
242
|
+
const metrics = getMetrics();
|
|
243
|
+
metrics.findingsTotal("high", "semgrep");
|
|
244
|
+
resetInMemoryStats();
|
|
245
|
+
metrics.findingsTotal("low", "gitleaks");
|
|
246
|
+
const stats = getInMemoryStats();
|
|
247
|
+
expect(stats.findings["gitleaks:low"]).toBe(1);
|
|
248
|
+
expect(stats.findings["semgrep:high"]).toBeUndefined();
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
describe("NoOpSpan", () => {
|
|
252
|
+
it("generates unique trace and span IDs", () => {
|
|
253
|
+
const tracer = getTracer();
|
|
254
|
+
const span1 = tracer.startSpan("span1");
|
|
255
|
+
const span2 = tracer.startSpan("span2");
|
|
256
|
+
const ctx1 = span1.getContext();
|
|
257
|
+
const ctx2 = span2.getContext();
|
|
258
|
+
expect(ctx1.traceId).not.toBe(ctx2.traceId);
|
|
259
|
+
expect(ctx1.spanId).not.toBe(ctx2.spanId);
|
|
260
|
+
});
|
|
261
|
+
it("generates hex format IDs", () => {
|
|
262
|
+
const tracer = getTracer();
|
|
263
|
+
const span = tracer.startSpan("test");
|
|
264
|
+
const ctx = span.getContext();
|
|
265
|
+
expect(ctx.traceId).toMatch(/^[0-9a-f]{32}$/);
|
|
266
|
+
expect(ctx.spanId).toMatch(/^[0-9a-f]{16}$/);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
//# sourceMappingURL=otel.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otel.test.js","sourceRoot":"","sources":["../../src/observability/otel.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.test.d.ts","sourceRoot":"","sources":["../../src/plugins/loader.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for plugin loader module
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { mkdir, rm, writeFile } from "fs/promises";
|
|
8
|
+
import { loadPlugins, executePluginDirect, PluginRegistry } from "./loader.js";
|
|
9
|
+
import { isValidManifest, validateManifest } from "./types.js";
|
|
10
|
+
describe("PluginRegistry", () => {
|
|
11
|
+
it("creates an empty registry", () => {
|
|
12
|
+
const registry = new PluginRegistry("/test/path");
|
|
13
|
+
expect(registry.getAll()).toHaveLength(0);
|
|
14
|
+
expect(registry.getEnabled()).toHaveLength(0);
|
|
15
|
+
expect(registry.getEntries()).toHaveLength(0);
|
|
16
|
+
});
|
|
17
|
+
it("adds and retrieves plugins", () => {
|
|
18
|
+
const registry = new PluginRegistry("/test/path");
|
|
19
|
+
const plugin = createMockPlugin("test-scanner");
|
|
20
|
+
registry.add(plugin);
|
|
21
|
+
expect(registry.has("test-scanner")).toBe(true);
|
|
22
|
+
expect(registry.get("test-scanner")).toBe(plugin);
|
|
23
|
+
expect(registry.getAll()).toHaveLength(1);
|
|
24
|
+
});
|
|
25
|
+
it("returns undefined for non-existent plugin", () => {
|
|
26
|
+
const registry = new PluginRegistry("/test/path");
|
|
27
|
+
expect(registry.get("non-existent")).toBeUndefined();
|
|
28
|
+
expect(registry.has("non-existent")).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
it("getEnabled filters out disabled and errored plugins", () => {
|
|
31
|
+
const registry = new PluginRegistry("/test/path");
|
|
32
|
+
registry.add(createMockPlugin("enabled-1", true));
|
|
33
|
+
registry.add(createMockPlugin("enabled-2", true));
|
|
34
|
+
registry.add(createMockPlugin("disabled", false));
|
|
35
|
+
registry.add(createMockPlugin("errored", true, "Load error"));
|
|
36
|
+
expect(registry.getAll()).toHaveLength(4);
|
|
37
|
+
expect(registry.getEnabled()).toHaveLength(2);
|
|
38
|
+
expect(registry.getEnabled().map((p) => p.manifest.name)).toEqual(["enabled-1", "enabled-2"]);
|
|
39
|
+
});
|
|
40
|
+
it("removes plugins", () => {
|
|
41
|
+
const registry = new PluginRegistry("/test/path");
|
|
42
|
+
registry.add(createMockPlugin("to-remove"));
|
|
43
|
+
registry.add(createMockPlugin("to-keep"));
|
|
44
|
+
const removed = registry.remove("to-remove");
|
|
45
|
+
expect(removed).toBe(true);
|
|
46
|
+
expect(registry.has("to-remove")).toBe(false);
|
|
47
|
+
expect(registry.has("to-keep")).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it("returns false when removing non-existent plugin", () => {
|
|
50
|
+
const registry = new PluginRegistry("/test/path");
|
|
51
|
+
expect(registry.remove("non-existent")).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it("clears all plugins", () => {
|
|
54
|
+
const registry = new PluginRegistry("/test/path");
|
|
55
|
+
registry.add(createMockPlugin("plugin-1"));
|
|
56
|
+
registry.add(createMockPlugin("plugin-2"));
|
|
57
|
+
registry.clear();
|
|
58
|
+
expect(registry.getAll()).toHaveLength(0);
|
|
59
|
+
expect(registry.has("plugin-1")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
it("getEntries returns registry entries", () => {
|
|
62
|
+
const registry = new PluginRegistry("/test/path");
|
|
63
|
+
registry.add(createMockPlugin("scanner-a", true));
|
|
64
|
+
registry.add(createMockPlugin("scanner-b", true, "Error message"));
|
|
65
|
+
const entries = registry.getEntries();
|
|
66
|
+
expect(entries).toHaveLength(2);
|
|
67
|
+
expect(entries[0].name).toBe("scanner-a");
|
|
68
|
+
expect(entries[0].loaded).toBe(true);
|
|
69
|
+
expect(entries[0].source).toBe("local");
|
|
70
|
+
expect(entries[1].name).toBe("scanner-b");
|
|
71
|
+
expect(entries[1].loaded).toBe(false);
|
|
72
|
+
expect(entries[1].error).toBe("Error message");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("loadPlugins", () => {
|
|
76
|
+
let testDir;
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
testDir = join(tmpdir(), `vaspera-plugin-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
79
|
+
await mkdir(testDir, { recursive: true });
|
|
80
|
+
});
|
|
81
|
+
afterEach(async () => {
|
|
82
|
+
await rm(testDir, { recursive: true, force: true });
|
|
83
|
+
});
|
|
84
|
+
it("returns empty registry when no plugins exist", async () => {
|
|
85
|
+
const registry = await loadPlugins(testDir);
|
|
86
|
+
expect(registry.getAll()).toHaveLength(0);
|
|
87
|
+
});
|
|
88
|
+
it("discovers local plugins", async () => {
|
|
89
|
+
// Create plugin structure
|
|
90
|
+
const pluginDir = join(testDir, ".vaspera", "plugins", "test-plugin");
|
|
91
|
+
await mkdir(pluginDir, { recursive: true });
|
|
92
|
+
const manifest = createValidManifest("test-plugin");
|
|
93
|
+
await writeFile(join(pluginDir, "manifest.json"), JSON.stringify(manifest));
|
|
94
|
+
// Create a dummy scanner module
|
|
95
|
+
await writeFile(join(pluginDir, "scanner.js"), `export default async function(ctx) { return { scanner: "test", findings: [], duration: 0, success: true }; }`);
|
|
96
|
+
const registry = await loadPlugins(testDir);
|
|
97
|
+
// The plugin may fail to load due to module resolution, but discovery works
|
|
98
|
+
expect(registry.getAll().length).toBeGreaterThanOrEqual(0);
|
|
99
|
+
});
|
|
100
|
+
it("skips directories without manifest.json", async () => {
|
|
101
|
+
const pluginDir = join(testDir, ".vaspera", "plugins", "invalid-plugin");
|
|
102
|
+
await mkdir(pluginDir, { recursive: true });
|
|
103
|
+
// No manifest.json created
|
|
104
|
+
const registry = await loadPlugins(testDir);
|
|
105
|
+
expect(registry.has("invalid-plugin")).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
it("respects skip option", async () => {
|
|
108
|
+
const plugin1Dir = join(testDir, ".vaspera", "plugins", "plugin-a");
|
|
109
|
+
const plugin2Dir = join(testDir, ".vaspera", "plugins", "plugin-b");
|
|
110
|
+
await mkdir(plugin1Dir, { recursive: true });
|
|
111
|
+
await mkdir(plugin2Dir, { recursive: true });
|
|
112
|
+
await writeFile(join(plugin1Dir, "manifest.json"), JSON.stringify(createValidManifest("plugin-a")));
|
|
113
|
+
await writeFile(join(plugin2Dir, "manifest.json"), JSON.stringify(createValidManifest("plugin-b")));
|
|
114
|
+
// Create scanner modules
|
|
115
|
+
const scannerCode = `export default async function(ctx) { return { scanner: "test", findings: [], duration: 0, success: true }; }`;
|
|
116
|
+
await writeFile(join(plugin1Dir, "scanner.js"), scannerCode);
|
|
117
|
+
await writeFile(join(plugin2Dir, "scanner.js"), scannerCode);
|
|
118
|
+
const registry = await loadPlugins(testDir, { skip: ["plugin-a"] });
|
|
119
|
+
expect(registry.has("plugin-a")).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
it("respects only option", async () => {
|
|
122
|
+
const plugin1Dir = join(testDir, ".vaspera", "plugins", "include-me");
|
|
123
|
+
const plugin2Dir = join(testDir, ".vaspera", "plugins", "exclude-me");
|
|
124
|
+
await mkdir(plugin1Dir, { recursive: true });
|
|
125
|
+
await mkdir(plugin2Dir, { recursive: true });
|
|
126
|
+
await writeFile(join(plugin1Dir, "manifest.json"), JSON.stringify(createValidManifest("include-me")));
|
|
127
|
+
await writeFile(join(plugin2Dir, "manifest.json"), JSON.stringify(createValidManifest("exclude-me")));
|
|
128
|
+
// Create scanner modules
|
|
129
|
+
const scannerCode = `export default async function(ctx) { return { scanner: "test", findings: [], duration: 0, success: true }; }`;
|
|
130
|
+
await writeFile(join(plugin1Dir, "scanner.js"), scannerCode);
|
|
131
|
+
await writeFile(join(plugin2Dir, "scanner.js"), scannerCode);
|
|
132
|
+
const registry = await loadPlugins(testDir, { only: ["include-me"] });
|
|
133
|
+
expect(registry.has("exclude-me")).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
it("respects loadLocal=false option", async () => {
|
|
136
|
+
const pluginDir = join(testDir, ".vaspera", "plugins", "local-plugin");
|
|
137
|
+
await mkdir(pluginDir, { recursive: true });
|
|
138
|
+
await writeFile(join(pluginDir, "manifest.json"), JSON.stringify(createValidManifest("local-plugin")));
|
|
139
|
+
await writeFile(join(pluginDir, "scanner.js"), `export default async () => ({});`);
|
|
140
|
+
const registry = await loadPlugins(testDir, { loadLocal: false });
|
|
141
|
+
expect(registry.has("local-plugin")).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
it("handles invalid manifest gracefully", async () => {
|
|
144
|
+
const pluginDir = join(testDir, ".vaspera", "plugins", "bad-manifest");
|
|
145
|
+
await mkdir(pluginDir, { recursive: true });
|
|
146
|
+
// Invalid manifest (missing required fields)
|
|
147
|
+
await writeFile(join(pluginDir, "manifest.json"), JSON.stringify({ name: "bad" }));
|
|
148
|
+
const registry = await loadPlugins(testDir);
|
|
149
|
+
// Should either not load or load with error
|
|
150
|
+
const plugin = registry.get("bad-manifest");
|
|
151
|
+
if (plugin) {
|
|
152
|
+
expect(plugin.loadError).toBeDefined();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
it("handles malformed JSON gracefully", async () => {
|
|
156
|
+
const pluginDir = join(testDir, ".vaspera", "plugins", "malformed");
|
|
157
|
+
await mkdir(pluginDir, { recursive: true });
|
|
158
|
+
await writeFile(join(pluginDir, "manifest.json"), "{ invalid json }");
|
|
159
|
+
// Should not throw
|
|
160
|
+
await expect(loadPlugins(testDir)).resolves.toBeDefined();
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
describe("executePluginDirect", () => {
|
|
164
|
+
it("executes plugin and returns result", async () => {
|
|
165
|
+
const plugin = createMockPlugin("direct-test", true, undefined, async () => ({
|
|
166
|
+
scanner: "semgrep",
|
|
167
|
+
findings: [{
|
|
168
|
+
scanner: "semgrep",
|
|
169
|
+
ruleId: "test-rule",
|
|
170
|
+
file: "a.js",
|
|
171
|
+
line: 1,
|
|
172
|
+
severity: "high",
|
|
173
|
+
message: "Test finding",
|
|
174
|
+
confidence: 90,
|
|
175
|
+
}],
|
|
176
|
+
duration: 100,
|
|
177
|
+
success: true,
|
|
178
|
+
}));
|
|
179
|
+
const context = {
|
|
180
|
+
projectPath: "/test",
|
|
181
|
+
timeout: 5000,
|
|
182
|
+
logger: createNoOpLogger(),
|
|
183
|
+
};
|
|
184
|
+
const result = await executePluginDirect(plugin, context);
|
|
185
|
+
expect(result.plugin).toBe("direct-test");
|
|
186
|
+
expect(result.sandboxed).toBe(false);
|
|
187
|
+
expect(result.result.success).toBe(true);
|
|
188
|
+
expect(result.result.findings).toHaveLength(1);
|
|
189
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(0);
|
|
190
|
+
});
|
|
191
|
+
it("handles plugin errors gracefully", async () => {
|
|
192
|
+
const plugin = createMockPlugin("error-plugin", true, undefined, async () => {
|
|
193
|
+
throw new Error("Scanner failed");
|
|
194
|
+
});
|
|
195
|
+
const context = {
|
|
196
|
+
projectPath: "/test",
|
|
197
|
+
timeout: 5000,
|
|
198
|
+
logger: createNoOpLogger(),
|
|
199
|
+
};
|
|
200
|
+
const result = await executePluginDirect(plugin, context);
|
|
201
|
+
expect(result.result.success).toBe(false);
|
|
202
|
+
expect(result.error).toContain("Scanner failed");
|
|
203
|
+
expect(result.result.findings).toHaveLength(0);
|
|
204
|
+
});
|
|
205
|
+
it("records duration correctly", async () => {
|
|
206
|
+
const plugin = createMockPlugin("duration-test", true, undefined, async () => {
|
|
207
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
208
|
+
return {
|
|
209
|
+
scanner: "duration-test",
|
|
210
|
+
findings: [],
|
|
211
|
+
duration: 50,
|
|
212
|
+
success: true,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
const context = {
|
|
216
|
+
projectPath: "/test",
|
|
217
|
+
timeout: 5000,
|
|
218
|
+
logger: createNoOpLogger(),
|
|
219
|
+
};
|
|
220
|
+
const result = await executePluginDirect(plugin, context);
|
|
221
|
+
expect(result.durationMs).toBeGreaterThanOrEqual(45); // Allow some variance
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
describe("isValidManifest", () => {
|
|
225
|
+
it("returns true for valid manifest", () => {
|
|
226
|
+
const manifest = createValidManifest("valid-plugin");
|
|
227
|
+
expect(isValidManifest(manifest)).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
it("returns false for invalid manifest", () => {
|
|
230
|
+
expect(isValidManifest({})).toBe(false);
|
|
231
|
+
expect(isValidManifest({ name: "test" })).toBe(false);
|
|
232
|
+
expect(isValidManifest(null)).toBe(false);
|
|
233
|
+
expect(isValidManifest(undefined)).toBe(false);
|
|
234
|
+
expect(isValidManifest("string")).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
it("validates name format", () => {
|
|
237
|
+
const manifest = createValidManifest("INVALID_NAME");
|
|
238
|
+
expect(isValidManifest(manifest)).toBe(false);
|
|
239
|
+
const validManifest = createValidManifest("valid-name-123");
|
|
240
|
+
expect(isValidManifest(validManifest)).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
it("validates version format", () => {
|
|
243
|
+
const manifest = createValidManifest("test");
|
|
244
|
+
manifest.version = "invalid";
|
|
245
|
+
expect(isValidManifest(manifest)).toBe(false);
|
|
246
|
+
manifest.version = "1.0.0";
|
|
247
|
+
expect(isValidManifest(manifest)).toBe(true);
|
|
248
|
+
manifest.version = "10.20.30-beta.1";
|
|
249
|
+
expect(isValidManifest(manifest)).toBe(true);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
describe("validateManifest", () => {
|
|
253
|
+
it("returns valid=true with manifest for valid input", () => {
|
|
254
|
+
const input = createValidManifest("test-plugin");
|
|
255
|
+
const result = validateManifest(input);
|
|
256
|
+
expect(result.valid).toBe(true);
|
|
257
|
+
expect(result.manifest).toBeDefined();
|
|
258
|
+
expect(result.manifest?.name).toBe("test-plugin");
|
|
259
|
+
expect(result.errors).toBeUndefined();
|
|
260
|
+
});
|
|
261
|
+
it("returns valid=false with errors for invalid input", () => {
|
|
262
|
+
const result = validateManifest({ name: "INVALID" });
|
|
263
|
+
expect(result.valid).toBe(false);
|
|
264
|
+
expect(result.manifest).toBeUndefined();
|
|
265
|
+
expect(result.errors).toBeDefined();
|
|
266
|
+
expect(result.errors?.length).toBeGreaterThan(0);
|
|
267
|
+
});
|
|
268
|
+
it("returns specific error messages for each validation failure", () => {
|
|
269
|
+
const result = validateManifest({
|
|
270
|
+
name: "INVALID",
|
|
271
|
+
version: "bad",
|
|
272
|
+
description: "",
|
|
273
|
+
scanner: {},
|
|
274
|
+
entryPoint: {},
|
|
275
|
+
});
|
|
276
|
+
expect(result.valid).toBe(false);
|
|
277
|
+
expect(result.errors?.some((e) => e.includes("name"))).toBe(true);
|
|
278
|
+
expect(result.errors?.some((e) => e.includes("version"))).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
it("applies default values for optional fields", () => {
|
|
281
|
+
const input = createValidManifest("defaults-test");
|
|
282
|
+
delete input.capabilities;
|
|
283
|
+
const result = validateManifest(input);
|
|
284
|
+
expect(result.valid).toBe(true);
|
|
285
|
+
expect(result.manifest?.capabilities).toBeDefined();
|
|
286
|
+
expect(result.manifest?.capabilities.sandboxable).toBe(true);
|
|
287
|
+
expect(result.manifest?.capabilities.autofix).toBe(false);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
// Helper functions
|
|
291
|
+
function createMockPlugin(name, enabled = true, loadError, scanFn) {
|
|
292
|
+
return {
|
|
293
|
+
manifest: createValidManifest(name),
|
|
294
|
+
path: `/mock/plugins/${name}`,
|
|
295
|
+
source: "local",
|
|
296
|
+
scan: scanFn || (async () => ({
|
|
297
|
+
scanner: name,
|
|
298
|
+
findings: [],
|
|
299
|
+
duration: 0,
|
|
300
|
+
success: true,
|
|
301
|
+
})),
|
|
302
|
+
enabled,
|
|
303
|
+
loadError,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function createValidManifest(name) {
|
|
307
|
+
return {
|
|
308
|
+
name,
|
|
309
|
+
version: "1.0.0",
|
|
310
|
+
description: `Test plugin: ${name}`,
|
|
311
|
+
scanner: {
|
|
312
|
+
type: "sast",
|
|
313
|
+
languages: ["javascript", "typescript"],
|
|
314
|
+
defaultTimeout: 60000,
|
|
315
|
+
requiresBinary: false,
|
|
316
|
+
},
|
|
317
|
+
entryPoint: {
|
|
318
|
+
module: "scanner.js",
|
|
319
|
+
exportName: "default",
|
|
320
|
+
},
|
|
321
|
+
capabilities: {
|
|
322
|
+
incremental: false,
|
|
323
|
+
autofix: false,
|
|
324
|
+
customRules: false,
|
|
325
|
+
sandboxable: true,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function createNoOpLogger() {
|
|
330
|
+
return {
|
|
331
|
+
debug: () => { },
|
|
332
|
+
info: () => { },
|
|
333
|
+
warn: () => { },
|
|
334
|
+
error: () => { },
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=loader.test.js.map
|