strray-ai 1.0.17 → 1.0.18

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.

Potentially problematic release.


This version of strray-ai might be problematic. Click here for more details.

Files changed (76) hide show
  1. package/opencode.json +1 -3
  2. package/package.json +2 -1
  3. package/scripts/postinstall.cjs +53 -0
  4. package/src/__tests__/agents/architect.test.ts +108 -0
  5. package/src/__tests__/agents/bug-triage-specialist.test.ts +246 -0
  6. package/src/__tests__/agents/code-reviewer.test.ts +242 -0
  7. package/src/__tests__/agents/enforcer.test.ts +179 -0
  8. package/src/__tests__/agents/index.test.ts +135 -0
  9. package/src/__tests__/agents/orchestrator.test.ts +228 -0
  10. package/src/__tests__/agents/refactorer.test.ts +248 -0
  11. package/src/__tests__/agents/security-auditor.test.ts +234 -0
  12. package/src/__tests__/agents/test-architect.test.ts +256 -0
  13. package/src/__tests__/agents/types.test.ts +210 -0
  14. package/src/__tests__/framework-enforcement-integration.test.ts +252 -0
  15. package/src/__tests__/framework-logger-persistence.test.ts +156 -0
  16. package/src/__tests__/integration/codex-enforcement-e2e.test.ts +195 -0
  17. package/src/__tests__/integration/codex-enforcement.test.ts +237 -0
  18. package/src/__tests__/integration/commit-batching-enforcement-integration.test.ts +343 -0
  19. package/src/__tests__/integration/context-providers-integration.test.ts +302 -0
  20. package/src/__tests__/integration/delegation-system.test.ts +263 -0
  21. package/src/__tests__/integration/e2e-framework-integration.test.ts +1524 -0
  22. package/src/__tests__/integration/framework-init.test.ts +327 -0
  23. package/src/__tests__/integration/json-codex-integration.test.ts +332 -0
  24. package/src/__tests__/integration/oh-my-opencode-integration.test.ts +240 -0
  25. package/src/__tests__/integration/orchestration-e2e.test.ts +176 -0
  26. package/src/__tests__/integration/orchestrator/basic-orchestrator.test.ts +53 -0
  27. package/src/__tests__/integration/orchestrator/concurrent-execution.test.ts +64 -0
  28. package/src/__tests__/integration/orchestrator/dependency-handling.test.ts +78 -0
  29. package/src/__tests__/integration/orchestrator-integration.test.ts.disabled +1279 -0
  30. package/src/__tests__/integration/postprocessor-integration.test.ts +64 -0
  31. package/src/__tests__/integration/security/security-integration.test.ts +160 -0
  32. package/src/__tests__/integration/server.test.ts +187 -0
  33. package/src/__tests__/integration/session-cleanup-validation.test.ts +612 -0
  34. package/src/__tests__/integration/session-lifecycle.test.ts +450 -0
  35. package/src/__tests__/integration/session-management.test.ts +168 -0
  36. package/src/__tests__/integration/session-monitoring-integration.test.ts +644 -0
  37. package/src/__tests__/integration/session-state-sharing.test.ts +601 -0
  38. package/src/__tests__/performance/enterprise-performance-tests.ts +1030 -0
  39. package/src/__tests__/performance/performance-system.test.ts +408 -0
  40. package/src/__tests__/plugins/marketplace-service.test.ts +1542 -0
  41. package/src/__tests__/plugins/marketplace-service.test.ts.disabled +1516 -0
  42. package/src/__tests__/postprocessor/escalation/EscalationEngine.test.ts +205 -0
  43. package/src/__tests__/postprocessor/success/SuccessHandler.test.ts +258 -0
  44. package/src/__tests__/setup.ts +220 -0
  45. package/src/__tests__/test-integration.ts +2 -0
  46. package/src/__tests__/test-processor.ts +1 -0
  47. package/src/__tests__/unit/agent-delegator.test.ts +949 -0
  48. package/src/__tests__/unit/analytics.test.ts +192 -0
  49. package/src/__tests__/unit/ast-code-parser.test.ts +342 -0
  50. package/src/__tests__/unit/benchmark.test.ts +201 -0
  51. package/src/__tests__/unit/blocked-test.test.ts +8 -0
  52. package/src/__tests__/unit/boot-orchestrator.test.ts +182 -0
  53. package/src/__tests__/unit/codebase-context-analyzer.test.ts +375 -0
  54. package/src/__tests__/unit/codex-injector.test.ts +282 -0
  55. package/src/__tests__/unit/codex-parser.test.ts +296 -0
  56. package/src/__tests__/unit/context-loader.test.ts +723 -0
  57. package/src/__tests__/unit/dependency-graph-builder.test.ts +531 -0
  58. package/src/__tests__/unit/monitoring.test.ts +115 -0
  59. package/src/__tests__/unit/orchestrator.test.ts +212 -0
  60. package/src/__tests__/unit/processor-activation.test.ts +878 -0
  61. package/src/__tests__/unit/rule-enforcer.test.ts +363 -0
  62. package/src/__tests__/unit/security/security-auditor.test.ts +226 -0
  63. package/src/__tests__/unit/security/security-hardener.test.ts +404 -0
  64. package/src/__tests__/unit/security/security-headers.test.ts +255 -0
  65. package/src/__tests__/unit/session-coordination-validator.test.ts +184 -0
  66. package/src/__tests__/unit/session-health-monitoring.test.ts +161 -0
  67. package/src/__tests__/unit/session-migration-logic.test.ts +153 -0
  68. package/src/__tests__/unit/session-migration-validator.test.ts +130 -0
  69. package/src/__tests__/unit/session-security-validator.test.ts +87 -0
  70. package/src/__tests__/unit/state-manager-persistence.test.ts +301 -0
  71. package/src/__tests__/unit/state-manager.test.ts +205 -0
  72. package/src/__tests__/utils/mock-framework.ts +392 -0
  73. package/src/__tests__/utils/mock-server.ts +72 -0
  74. package/src/__tests__/utils/test-data.ts +130 -0
  75. package/src/__tests__/utils/test-helpers.ts +640 -0
  76. package/src/__tests__/utils/test-utils.ts +241 -0
@@ -0,0 +1,404 @@
1
+ /**
2
+ * StringRay AI v1.0.9 - SecurityHardener Unit Tests
3
+ *
4
+ * Tests for SecurityHardener including:
5
+ * - Security hardening rule application
6
+ * - Audit result processing
7
+ * - Input validation functionality
8
+ * - Rate limiting and security event logging
9
+ *
10
+ * @version 1.0.0
11
+ * @since 2026-01-07
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
15
+ import { SecurityHardener } from "../../../security/security-hardener";
16
+ import { SecurityIssue } from "../../../security/security-auditor";
17
+
18
+ // Mock fs module
19
+ vi.mock("fs", () => ({
20
+ promises: {
21
+ chmod: vi.fn().mockResolvedValue(undefined),
22
+ },
23
+ }));
24
+
25
+ import { promises as fs } from "fs";
26
+
27
+ describe("SecurityHardener", () => {
28
+ let hardener: SecurityHardener;
29
+
30
+ beforeEach(() => {
31
+ vi.clearAllMocks();
32
+ hardener = new SecurityHardener();
33
+ });
34
+
35
+ describe("hardenSecurity", () => {
36
+ it("should apply fixes for hardcoded secrets issues", async () => {
37
+ const issues: SecurityIssue[] = [
38
+ {
39
+ severity: "high",
40
+ category: "hardcoded-secrets",
41
+ file: "/test/config.ts",
42
+ description: "Hardcoded API key detected",
43
+ recommendation: "Move to environment variables",
44
+ },
45
+ ];
46
+
47
+ const result = await hardener.hardenSecurity({ issues });
48
+
49
+ expect(result.appliedFixes).toHaveLength(0); // Manual intervention required
50
+ expect(result.remainingIssues).toHaveLength(1);
51
+ if (result.remainingIssues[0]) {
52
+ expect(result.remainingIssues[0].category).toBe("hardcoded-secrets");
53
+ }
54
+ });
55
+
56
+ it("should apply fixes for file permission issues", async () => {
57
+ const issues: SecurityIssue[] = [
58
+ {
59
+ severity: "high",
60
+ category: "file-permissions",
61
+ file: "/test/world-writable.txt",
62
+ description: "File is world-writable",
63
+ recommendation: "Restrict permissions",
64
+ },
65
+ ];
66
+
67
+ const result = await hardener.hardenSecurity({ issues });
68
+
69
+ expect(result.appliedFixes).toHaveLength(1);
70
+ if (result.appliedFixes[0]) {
71
+ expect(result.appliedFixes[0]).toContain("Fixed file permissions");
72
+ }
73
+ expect(result.remainingIssues).toHaveLength(0);
74
+ expect(fs.chmod).toHaveBeenCalledWith("/test/world-writable.txt", 0o644);
75
+ });
76
+
77
+ it("should handle file permission fix failures", async () => {
78
+ const issues: SecurityIssue[] = [
79
+ {
80
+ severity: "high" as const,
81
+ category: "file-permissions",
82
+ file: "/test/world-writable.txt",
83
+ description: "File is world-writable",
84
+ recommendation: "Restrict permissions",
85
+ },
86
+ ];
87
+
88
+ vi.mocked(fs.chmod).mockRejectedValueOnce(new Error("Permission denied"));
89
+
90
+ const result = await hardener.hardenSecurity({ issues });
91
+
92
+ expect(result.appliedFixes).toHaveLength(0);
93
+ expect(result.remainingIssues).toHaveLength(1);
94
+ if (result.remainingIssues[0]) {
95
+ expect(result.remainingIssues[0].description).toContain(
96
+ "File is world-writable",
97
+ );
98
+ }
99
+ });
100
+
101
+ it("should handle dependency management issues", async () => {
102
+ const issues: SecurityIssue[] = [
103
+ {
104
+ severity: "medium",
105
+ category: "dependency-management",
106
+ file: "/test/package.json",
107
+ description: "Insecure dependency version",
108
+ recommendation: "Update to specific version",
109
+ },
110
+ ];
111
+
112
+ const result = await hardener.hardenSecurity({ issues });
113
+
114
+ expect(result.appliedFixes).toHaveLength(0); // Manual intervention required
115
+ expect(result.remainingIssues).toHaveLength(1);
116
+ });
117
+
118
+ it("should handle input validation issues", async () => {
119
+ const issues: SecurityIssue[] = [
120
+ {
121
+ severity: "medium",
122
+ category: "input-validation",
123
+ file: "/test/api.ts",
124
+ line: 10,
125
+ description: "Missing input validation",
126
+ recommendation: "Add validation",
127
+ },
128
+ ];
129
+
130
+ const result = await hardener.hardenSecurity({ issues });
131
+
132
+ expect(result.appliedFixes).toHaveLength(0); // Code modification required
133
+ expect(result.remainingIssues).toHaveLength(1);
134
+ });
135
+
136
+ it("should handle multiple issues of different types", async () => {
137
+ const issues: SecurityIssue[] = [
138
+ {
139
+ severity: "high",
140
+ category: "file-permissions",
141
+ file: "/test/file1.txt",
142
+ description: "World-writable file",
143
+ recommendation: "Fix permissions",
144
+ },
145
+ {
146
+ severity: "high",
147
+ category: "hardcoded-secrets",
148
+ file: "/test/config.ts",
149
+ description: "Hardcoded secret",
150
+ recommendation: "Move to env vars",
151
+ },
152
+ {
153
+ severity: "medium",
154
+ category: "input-validation",
155
+ file: "/test/api.ts",
156
+ description: "Missing validation",
157
+ recommendation: "Add validation",
158
+ },
159
+ ];
160
+
161
+ const result = await hardener.hardenSecurity({ issues });
162
+
163
+ expect(result.appliedFixes).toHaveLength(1); // Only file permissions can be auto-fixed
164
+ if (result.appliedFixes[0]) {
165
+ expect(result.appliedFixes[0]).toContain("Fixed file permissions");
166
+ }
167
+ expect(result.remainingIssues).toHaveLength(2); // Secrets and input validation require manual fixes
168
+ });
169
+ });
170
+
171
+ describe("addSecurityHeaders", () => {
172
+ it("should add security headers when enabled", () => {
173
+ const headers: Record<string, string> = {
174
+ "Content-Type": "application/json",
175
+ };
176
+
177
+ const secureHeaders = hardener.addSecurityHeaders(headers);
178
+
179
+ expect(secureHeaders).toHaveProperty("X-Content-Type-Options", "nosniff");
180
+ expect(secureHeaders).toHaveProperty("X-Frame-Options", "DENY");
181
+ expect(secureHeaders).toHaveProperty("X-XSS-Protection", "1; mode=block");
182
+ expect(secureHeaders).toHaveProperty("Strict-Transport-Security");
183
+ expect(secureHeaders).toHaveProperty("Content-Security-Policy");
184
+ expect(secureHeaders).toHaveProperty("Referrer-Policy");
185
+ expect(secureHeaders["Content-Type"]).toBe("application/json"); // Original headers preserved
186
+ });
187
+
188
+ it("should not add headers when disabled", () => {
189
+ const disabledHardener = new SecurityHardener({
190
+ enableSecureHeaders: false,
191
+ });
192
+
193
+ const headers: Record<string, string> = {
194
+ "Content-Type": "application/json",
195
+ };
196
+
197
+ const secureHeaders = disabledHardener.addSecurityHeaders(headers);
198
+
199
+ expect(secureHeaders).toEqual(headers); // No additional headers added
200
+ });
201
+ });
202
+
203
+ describe("validateInput", () => {
204
+ it("should validate string input successfully", () => {
205
+ const schema = { type: "string", maxLength: 10 };
206
+ const result = hardener.validateInput("test", schema);
207
+
208
+ expect(result.valid).toBe(true);
209
+ expect(result.errors).toHaveLength(0);
210
+ });
211
+
212
+ it("should reject invalid string type", () => {
213
+ const schema = { type: "string" };
214
+ const result = hardener.validateInput(123, schema);
215
+
216
+ expect(result.valid).toBe(false);
217
+ expect(result.errors).toContain("Expected string");
218
+ });
219
+
220
+ it("should reject string exceeding max length", () => {
221
+ const schema = { type: "string", maxLength: 5 };
222
+ const result = hardener.validateInput("this is too long", schema);
223
+
224
+ expect(result.valid).toBe(false);
225
+ expect(result.errors).toContain("String too long (max 5)");
226
+ });
227
+
228
+ it("should validate pattern matching", () => {
229
+ const schema = { type: "string", pattern: "^[A-Z]+$" };
230
+ const result = hardener.validateInput("ABC", schema);
231
+
232
+ expect(result.valid).toBe(true);
233
+ });
234
+
235
+ it("should reject pattern mismatch", () => {
236
+ const schema = { type: "string", pattern: "^[A-Z]+$" };
237
+ const result = hardener.validateInput("abc123", schema);
238
+
239
+ expect(result.valid).toBe(false);
240
+ expect(result.errors).toContain("String does not match required pattern");
241
+ });
242
+
243
+ it("should skip validation when disabled", () => {
244
+ const disabledHardener = new SecurityHardener({
245
+ enableInputValidation: false,
246
+ });
247
+
248
+ const schema = { type: "string" };
249
+ const result = disabledHardener.validateInput(123, schema);
250
+
251
+ expect(result.valid).toBe(true); // Always valid when disabled
252
+ expect(result.errors).toHaveLength(0);
253
+ });
254
+ });
255
+
256
+ describe("checkRateLimit", () => {
257
+ let rateLimitMap: Map<string, number[]>;
258
+
259
+ beforeEach(() => {
260
+ rateLimitMap = new Map();
261
+ });
262
+
263
+ it("should allow requests within limit", () => {
264
+ const identifier = "user1";
265
+
266
+ for (let i = 0; i < 5; i++) {
267
+ const allowed = hardener.checkRateLimit(identifier, rateLimitMap);
268
+ expect(allowed).toBe(true);
269
+ }
270
+
271
+ expect(rateLimitMap.get(identifier)).toHaveLength(5);
272
+ });
273
+
274
+ it("should block requests exceeding limit", () => {
275
+ const identifier = "user1";
276
+
277
+ // Fill up the rate limit
278
+ for (let i = 0; i < 100; i++) {
279
+ hardener.checkRateLimit(identifier, rateLimitMap);
280
+ }
281
+
282
+ // Next request should be blocked
283
+ const blocked = hardener.checkRateLimit(identifier, rateLimitMap);
284
+ expect(blocked).toBe(false);
285
+ });
286
+
287
+ it("should allow requests after window expires", () => {
288
+ const identifier = "user1";
289
+
290
+ // Mock Date.now to simulate time passing
291
+ const originalNow = Date.now;
292
+ let currentTime = 1000000000;
293
+
294
+ vi.spyOn(Date, "now").mockImplementation(() => currentTime);
295
+
296
+ // Fill rate limit
297
+ for (let i = 0; i < 100; i++) {
298
+ hardener.checkRateLimit(identifier, rateLimitMap);
299
+ }
300
+
301
+ // Advance time past the window (60 seconds)
302
+ currentTime += 61000;
303
+
304
+ // Should allow new requests
305
+ const allowed = hardener.checkRateLimit(identifier, rateLimitMap);
306
+ expect(allowed).toBe(true);
307
+
308
+ vi.restoreAllMocks();
309
+ });
310
+
311
+ it("should skip rate limiting when disabled", () => {
312
+ const disabledHardener = new SecurityHardener({
313
+ enableRateLimiting: false,
314
+ });
315
+
316
+ const identifier = "user1";
317
+ const allowed = disabledHardener.checkRateLimit(identifier, rateLimitMap);
318
+
319
+ expect(allowed).toBe(true);
320
+ expect(rateLimitMap.has(identifier)).toBe(false); // No tracking when disabled
321
+ });
322
+ });
323
+
324
+ describe("logSecurityEvent", () => {
325
+ let consoleSpy: any;
326
+
327
+ beforeEach(() => {
328
+ consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
329
+ });
330
+
331
+ afterEach(() => {
332
+ consoleSpy.mockRestore();
333
+ });
334
+
335
+ it("should log security events when enabled", () => {
336
+ const event = {
337
+ type: "rate_limit_exceeded",
338
+ severity: "medium" as const,
339
+ message: "Rate limit exceeded for user",
340
+ metadata: { userId: "123", ip: "192.168.1.1" },
341
+ };
342
+
343
+ hardener.logSecurityEvent(event);
344
+
345
+ expect(consoleSpy).toHaveBeenCalledWith(
346
+ "🔒 SECURITY EVENT [MEDIUM]:",
347
+ expect.stringContaining('"type":"rate_limit_exceeded"'),
348
+ );
349
+
350
+ const loggedData = JSON.parse(consoleSpy.mock.calls[0][1]);
351
+ expect(loggedData.type).toBe("rate_limit_exceeded");
352
+ expect(loggedData.severity).toBe("medium");
353
+ expect(loggedData.timestamp).toBeDefined();
354
+ expect(loggedData.metadata.userId).toBe("123");
355
+ });
356
+
357
+ it("should not log when disabled", () => {
358
+ const disabledHardener = new SecurityHardener({
359
+ enableAuditLogging: false,
360
+ });
361
+
362
+ const event = {
363
+ type: "test_event",
364
+ severity: "low" as const,
365
+ message: "Test event",
366
+ };
367
+
368
+ disabledHardener.logSecurityEvent(event);
369
+
370
+ expect(consoleSpy).not.toHaveBeenCalled();
371
+ });
372
+ });
373
+
374
+ describe("Configuration", () => {
375
+ it("should use default configuration", () => {
376
+ const defaultHardener = new SecurityHardener();
377
+
378
+ // Test that default values are applied by checking behavior
379
+ const headers = defaultHardener.addSecurityHeaders({});
380
+ expect(Object.keys(headers)).toHaveLength(6); // All security headers added
381
+ });
382
+
383
+ it("should accept custom configuration", () => {
384
+ const customConfig = {
385
+ enableInputValidation: false,
386
+ enableRateLimiting: false,
387
+ enableAuditLogging: false,
388
+ enableSecureHeaders: false,
389
+ maxRequestSizeBytes: 2048,
390
+ rateLimitWindowMs: 30000,
391
+ rateLimitMaxRequests: 50,
392
+ };
393
+
394
+ const customHardener = new SecurityHardener(customConfig);
395
+
396
+ // Test configuration is applied
397
+ const headers = customHardener.addSecurityHeaders({ test: "value" });
398
+ expect(headers).toEqual({ test: "value" }); // No security headers added
399
+
400
+ const validation = customHardener.validateInput(123, { type: "string" });
401
+ expect(validation.valid).toBe(true); // Validation disabled
402
+ });
403
+ });
404
+ });
@@ -0,0 +1,255 @@
1
+ /**
2
+ * StringRay AI v1.0.9 - SecurityHeadersMiddleware Unit Tests
3
+ *
4
+ * Tests for SecurityHeadersMiddleware including:
5
+ * - Header validation and application
6
+ * - Express middleware integration
7
+ * - Fastify middleware integration
8
+ * - Configuration management
9
+ *
10
+ * @version 1.0.0
11
+ * @since 2026-01-07
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, vi } from "vitest";
15
+ import { SecurityHeadersMiddleware } from "../../../security/security-headers";
16
+
17
+ describe("SecurityHeadersMiddleware", () => {
18
+ let middleware: SecurityHeadersMiddleware;
19
+ let mockResponse: any;
20
+
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ middleware = new SecurityHeadersMiddleware();
24
+
25
+ mockResponse = {
26
+ setHeader: vi.fn(),
27
+ };
28
+ });
29
+
30
+ describe("applySecurityHeaders", () => {
31
+ it("should apply all default security headers", () => {
32
+ middleware.applySecurityHeaders(mockResponse);
33
+
34
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
35
+ "Content-Security-Policy",
36
+ expect.stringContaining("default-src"),
37
+ );
38
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
39
+ "Strict-Transport-Security",
40
+ expect.stringContaining("max-age"),
41
+ );
42
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
43
+ "X-Frame-Options",
44
+ "DENY",
45
+ );
46
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
47
+ "X-XSS-Protection",
48
+ "1; mode=block",
49
+ );
50
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
51
+ "X-Content-Type-Options",
52
+ "nosniff",
53
+ );
54
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
55
+ "Referrer-Policy",
56
+ "strict-origin-when-cross-origin",
57
+ );
58
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
59
+ "Permissions-Policy",
60
+ expect.stringContaining("geolocation"),
61
+ );
62
+ });
63
+
64
+ it("should handle invalid response object gracefully", () => {
65
+ const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
66
+
67
+ middleware.applySecurityHeaders(null as any);
68
+ middleware.applySecurityHeaders({} as any);
69
+
70
+ expect(consoleSpy).toHaveBeenCalledWith(
71
+ "SecurityHeadersMiddleware: Invalid response object",
72
+ );
73
+ consoleSpy.mockRestore();
74
+ });
75
+
76
+ it("should apply custom CSP when configured", () => {
77
+ const customCSP = "default-src 'self'; script-src 'self' 'unsafe-eval'";
78
+ const customMiddleware = new SecurityHeadersMiddleware({
79
+ customCSP,
80
+ });
81
+
82
+ customMiddleware.applySecurityHeaders(mockResponse);
83
+
84
+ expect(mockResponse.setHeader).toHaveBeenCalledWith(
85
+ "Content-Security-Policy",
86
+ customCSP,
87
+ );
88
+ });
89
+
90
+ it("should skip HSTS when disabled", () => {
91
+ const customMiddleware = new SecurityHeadersMiddleware({
92
+ enableHSTS: false,
93
+ });
94
+
95
+ const freshMockResponse = { setHeader: vi.fn() };
96
+ customMiddleware.applySecurityHeaders(freshMockResponse);
97
+
98
+ const hstsCalls = freshMockResponse.setHeader.mock.calls.filter(
99
+ ([key]) => key === "Strict-Transport-Security",
100
+ );
101
+ expect(hstsCalls).toHaveLength(0);
102
+ });
103
+
104
+ it("should configure HSTS with custom max age", () => {
105
+ const customMiddleware = new SecurityHeadersMiddleware({
106
+ hstsMaxAge: 86400, // 1 day
107
+ });
108
+
109
+ const freshMockResponse = { setHeader: vi.fn() };
110
+ customMiddleware.applySecurityHeaders(freshMockResponse);
111
+
112
+ expect(freshMockResponse.setHeader).toHaveBeenCalledWith(
113
+ "Strict-Transport-Security",
114
+ "max-age=86400; includeSubDomains",
115
+ );
116
+ });
117
+
118
+ it("should include preload in HSTS when configured", () => {
119
+ const customMiddleware = new SecurityHeadersMiddleware({
120
+ hstsPreload: true,
121
+ });
122
+
123
+ const freshMockResponse = { setHeader: vi.fn() };
124
+ customMiddleware.applySecurityHeaders(freshMockResponse);
125
+
126
+ expect(freshMockResponse.setHeader).toHaveBeenCalledWith(
127
+ "Strict-Transport-Security",
128
+ expect.stringContaining("preload"),
129
+ );
130
+ });
131
+
132
+ it("should skip frame options when disabled", () => {
133
+ const customMiddleware = new SecurityHeadersMiddleware({
134
+ enableFrameOptions: false,
135
+ });
136
+
137
+ const freshMockResponse = { setHeader: vi.fn() };
138
+ customMiddleware.applySecurityHeaders(freshMockResponse);
139
+
140
+ const frameCalls = freshMockResponse.setHeader.mock.calls.filter(
141
+ ([key]) => key === "X-Frame-Options",
142
+ );
143
+ expect(frameCalls).toHaveLength(0);
144
+ });
145
+ });
146
+
147
+ describe("Express Middleware", () => {
148
+ it("should return valid Express middleware function", () => {
149
+ const expressMiddleware = middleware.getExpressMiddleware();
150
+
151
+ expect(typeof expressMiddleware).toBe("function");
152
+ expect(expressMiddleware.length).toBe(3); // Express middleware signature
153
+ });
154
+
155
+ it("should apply headers in Express middleware", () => {
156
+ const expressMiddleware = middleware.getExpressMiddleware();
157
+ const mockReq = {};
158
+ const mockRes = { setHeader: vi.fn() };
159
+ const mockNext = vi.fn();
160
+
161
+ expressMiddleware(mockReq, mockRes, mockNext);
162
+
163
+ expect(mockRes.setHeader).toHaveBeenCalled();
164
+ expect(mockNext).toHaveBeenCalled();
165
+ });
166
+ });
167
+
168
+ describe("Fastify Middleware", () => {
169
+ it("should return valid Fastify middleware function", () => {
170
+ const fastifyMiddleware = middleware.getFastifyMiddleware();
171
+
172
+ expect(typeof fastifyMiddleware).toBe("function");
173
+ expect(fastifyMiddleware.length).toBe(3); // Fastify middleware signature
174
+ });
175
+
176
+ it("should apply headers in Fastify middleware", () => {
177
+ const fastifyMiddleware = middleware.getFastifyMiddleware();
178
+ const mockRequest = {};
179
+ const mockReply = { setHeader: vi.fn() };
180
+ const mockDone = vi.fn();
181
+
182
+ fastifyMiddleware(mockRequest, mockReply, mockDone);
183
+
184
+ expect(mockReply.setHeader).toHaveBeenCalled();
185
+ expect(mockDone).toHaveBeenCalled();
186
+ });
187
+ });
188
+
189
+ describe("Generic Middleware", () => {
190
+ it("should return bound applySecurityHeaders function", () => {
191
+ const genericMiddleware = middleware.getMiddleware();
192
+
193
+ expect(typeof genericMiddleware).toBe("function");
194
+ expect(genericMiddleware.length).toBe(1);
195
+ });
196
+
197
+ it("should apply headers when called directly", () => {
198
+ const genericMiddleware = middleware.getMiddleware();
199
+
200
+ genericMiddleware(mockResponse);
201
+
202
+ expect(mockResponse.setHeader).toHaveBeenCalled();
203
+ });
204
+ });
205
+
206
+ describe("Configuration Management", () => {
207
+ it("should update configuration", () => {
208
+ const newConfig = {
209
+ enableCSP: false,
210
+ enableHSTS: false,
211
+ customCSP: "custom-policy",
212
+ };
213
+
214
+ middleware.updateConfig(newConfig);
215
+
216
+ const config = middleware.getConfig();
217
+ expect(config.enableCSP).toBe(false);
218
+ expect(config.enableHSTS).toBe(false);
219
+ expect(config.customCSP).toBe("custom-policy");
220
+ });
221
+
222
+ it("should return current configuration", () => {
223
+ const config = middleware.getConfig();
224
+
225
+ expect(config).toHaveProperty("enableCSP");
226
+ expect(config).toHaveProperty("enableHSTS");
227
+ expect(config).toHaveProperty("enableFrameOptions");
228
+ expect(config).toHaveProperty("hstsMaxAge");
229
+ expect(typeof config.enableCSP).toBe("boolean");
230
+ expect(typeof config.hstsMaxAge).toBe("number");
231
+ });
232
+
233
+ it("should preserve existing config when updating partially", () => {
234
+ const originalConfig = middleware.getConfig();
235
+ const originalCSP = originalConfig.enableCSP;
236
+
237
+ middleware.updateConfig({ enableHSTS: false });
238
+
239
+ const newConfig = middleware.getConfig();
240
+ expect(newConfig.enableCSP).toBe(originalCSP);
241
+ expect(newConfig.enableHSTS).toBe(false);
242
+ });
243
+ });
244
+
245
+ describe("Integration with SecurityHardener", () => {
246
+ it("should work with security hardener headers", () => {
247
+ // This test verifies that the middleware integrates properly
248
+ // with the security hardener by checking that headers are applied
249
+ middleware.applySecurityHeaders(mockResponse);
250
+
251
+ // Verify that multiple headers are set (including those from security hardener)
252
+ expect(mockResponse.setHeader).toHaveBeenCalledTimes(7); // All default headers
253
+ });
254
+ });
255
+ });