vaspera 2.11.0 → 2.13.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/__tests__/audit-trail.test.d.ts +7 -0
  3. package/dist/__tests__/audit-trail.test.d.ts.map +1 -0
  4. package/dist/__tests__/audit-trail.test.js +336 -0
  5. package/dist/__tests__/audit-trail.test.js.map +1 -0
  6. package/dist/__tests__/property-test-helpers.d.ts +1 -1
  7. package/dist/__tests__/siem-integration.test.d.ts +7 -0
  8. package/dist/__tests__/siem-integration.test.d.ts.map +1 -0
  9. package/dist/__tests__/siem-integration.test.js +285 -0
  10. package/dist/__tests__/siem-integration.test.js.map +1 -0
  11. package/dist/action/pr-comment.test.js +1 -0
  12. package/dist/action/pr-comment.test.js.map +1 -1
  13. package/dist/action/sarif-upload.test.js +1 -0
  14. package/dist/action/sarif-upload.test.js.map +1 -1
  15. package/dist/autofix/ast/__tests__/typescript.test.d.ts +5 -0
  16. package/dist/autofix/ast/__tests__/typescript.test.d.ts.map +1 -0
  17. package/dist/autofix/ast/__tests__/typescript.test.js +210 -0
  18. package/dist/autofix/ast/__tests__/typescript.test.js.map +1 -0
  19. package/dist/autofix/ast/index.d.ts +11 -0
  20. package/dist/autofix/ast/index.d.ts.map +1 -0
  21. package/dist/autofix/ast/index.js +11 -0
  22. package/dist/autofix/ast/index.js.map +1 -0
  23. package/dist/autofix/ast/types.d.ts +77 -0
  24. package/dist/autofix/ast/types.d.ts.map +1 -0
  25. package/dist/autofix/ast/types.js +9 -0
  26. package/dist/autofix/ast/types.js.map +1 -0
  27. package/dist/autofix/ast/typescript.d.ts +17 -0
  28. package/dist/autofix/ast/typescript.d.ts.map +1 -0
  29. package/dist/autofix/ast/typescript.js +427 -0
  30. package/dist/autofix/ast/typescript.js.map +1 -0
  31. package/dist/autofix/constitution.schema.d.ts +21 -21
  32. package/dist/autofix/index.d.ts +1 -0
  33. package/dist/autofix/index.d.ts.map +1 -1
  34. package/dist/autofix/index.js +2 -0
  35. package/dist/autofix/index.js.map +1 -1
  36. package/dist/config/flags.d.ts +6 -6
  37. package/dist/history/store.d.ts +55 -1
  38. package/dist/history/store.d.ts.map +1 -1
  39. package/dist/history/store.js +152 -4
  40. package/dist/history/store.js.map +1 -1
  41. package/dist/history/types.d.ts +9 -5
  42. package/dist/history/types.d.ts.map +1 -1
  43. package/dist/history/verify.d.ts.map +1 -1
  44. package/dist/history/verify.js +5 -3
  45. package/dist/history/verify.js.map +1 -1
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +923 -16
  48. package/dist/index.js.map +1 -1
  49. package/dist/integrations/siem/datadog.d.ts +44 -0
  50. package/dist/integrations/siem/datadog.d.ts.map +1 -0
  51. package/dist/integrations/siem/datadog.js +211 -0
  52. package/dist/integrations/siem/datadog.js.map +1 -0
  53. package/dist/integrations/siem/format.d.ts +59 -0
  54. package/dist/integrations/siem/format.d.ts.map +1 -0
  55. package/dist/integrations/siem/format.js +360 -0
  56. package/dist/integrations/siem/format.js.map +1 -0
  57. package/dist/integrations/siem/index.d.ts +56 -0
  58. package/dist/integrations/siem/index.d.ts.map +1 -0
  59. package/dist/integrations/siem/index.js +117 -0
  60. package/dist/integrations/siem/index.js.map +1 -0
  61. package/dist/integrations/siem/sentinel.d.ts +53 -0
  62. package/dist/integrations/siem/sentinel.d.ts.map +1 -0
  63. package/dist/integrations/siem/sentinel.js +231 -0
  64. package/dist/integrations/siem/sentinel.js.map +1 -0
  65. package/dist/integrations/siem/splunk.d.ts +46 -0
  66. package/dist/integrations/siem/splunk.d.ts.map +1 -0
  67. package/dist/integrations/siem/splunk.js +210 -0
  68. package/dist/integrations/siem/splunk.js.map +1 -0
  69. package/dist/integrations/siem/types.d.ts +210 -0
  70. package/dist/integrations/siem/types.d.ts.map +1 -0
  71. package/dist/integrations/siem/types.js +9 -0
  72. package/dist/integrations/siem/types.js.map +1 -0
  73. package/dist/persistence/__tests__/json-fallback.test.d.ts +5 -0
  74. package/dist/persistence/__tests__/json-fallback.test.d.ts.map +1 -0
  75. package/dist/persistence/__tests__/json-fallback.test.js +249 -0
  76. package/dist/persistence/__tests__/json-fallback.test.js.map +1 -0
  77. package/dist/persistence/__tests__/persistence.test.d.ts +5 -0
  78. package/dist/persistence/__tests__/persistence.test.d.ts.map +1 -0
  79. package/dist/persistence/__tests__/persistence.test.js +369 -0
  80. package/dist/persistence/__tests__/persistence.test.js.map +1 -0
  81. package/dist/persistence/db.d.ts +30 -0
  82. package/dist/persistence/db.d.ts.map +1 -0
  83. package/dist/persistence/db.js +128 -0
  84. package/dist/persistence/db.js.map +1 -0
  85. package/dist/persistence/index.d.ts +75 -0
  86. package/dist/persistence/index.d.ts.map +1 -0
  87. package/dist/persistence/index.js +268 -0
  88. package/dist/persistence/index.js.map +1 -0
  89. package/dist/persistence/json-fallback.d.ts +52 -0
  90. package/dist/persistence/json-fallback.d.ts.map +1 -0
  91. package/dist/persistence/json-fallback.js +283 -0
  92. package/dist/persistence/json-fallback.js.map +1 -0
  93. package/dist/persistence/migrations/index.d.ts +10 -0
  94. package/dist/persistence/migrations/index.d.ts.map +1 -0
  95. package/dist/persistence/migrations/index.js +125 -0
  96. package/dist/persistence/migrations/index.js.map +1 -0
  97. package/dist/persistence/repositories/findings.d.ts +41 -0
  98. package/dist/persistence/repositories/findings.d.ts.map +1 -0
  99. package/dist/persistence/repositories/findings.js +238 -0
  100. package/dist/persistence/repositories/findings.js.map +1 -0
  101. package/dist/persistence/repositories/projects.d.ts +22 -0
  102. package/dist/persistence/repositories/projects.d.ts.map +1 -0
  103. package/dist/persistence/repositories/projects.js +71 -0
  104. package/dist/persistence/repositories/projects.js.map +1 -0
  105. package/dist/persistence/repositories/scans.d.ts +30 -0
  106. package/dist/persistence/repositories/scans.d.ts.map +1 -0
  107. package/dist/persistence/repositories/scans.js +107 -0
  108. package/dist/persistence/repositories/scans.js.map +1 -0
  109. package/dist/persistence/repositories/trends.d.ts +42 -0
  110. package/dist/persistence/repositories/trends.d.ts.map +1 -0
  111. package/dist/persistence/repositories/trends.js +178 -0
  112. package/dist/persistence/repositories/trends.js.map +1 -0
  113. package/dist/persistence/types.d.ts +105 -0
  114. package/dist/persistence/types.d.ts.map +1 -0
  115. package/dist/persistence/types.js +13 -0
  116. package/dist/persistence/types.js.map +1 -0
  117. package/dist/plugins/types.d.ts +2 -2
  118. package/dist/scanners/ai-code/index.d.ts.map +1 -1
  119. package/dist/scanners/ai-code/index.js +90 -2
  120. package/dist/scanners/ai-code/index.js.map +1 -1
  121. package/dist/scanners/ai-code/types.d.ts +24 -12
  122. package/dist/scanners/ai-code/types.d.ts.map +1 -1
  123. package/dist/scanners/cache.d.ts.map +1 -1
  124. package/dist/scanners/cache.js +1 -0
  125. package/dist/scanners/cache.js.map +1 -1
  126. package/dist/scanners/deploy/types.d.ts +13 -13
  127. package/dist/scanners/detection/__tests__/detection.test.d.ts +5 -0
  128. package/dist/scanners/detection/__tests__/detection.test.d.ts.map +1 -0
  129. package/dist/scanners/detection/__tests__/detection.test.js +265 -0
  130. package/dist/scanners/detection/__tests__/detection.test.js.map +1 -0
  131. package/dist/scanners/detection/engines/ast-query.d.ts +23 -0
  132. package/dist/scanners/detection/engines/ast-query.d.ts.map +1 -0
  133. package/dist/scanners/detection/engines/ast-query.js +232 -0
  134. package/dist/scanners/detection/engines/ast-query.js.map +1 -0
  135. package/dist/scanners/detection/engines/data-flow.d.ts +12 -0
  136. package/dist/scanners/detection/engines/data-flow.d.ts.map +1 -0
  137. package/dist/scanners/detection/engines/data-flow.js +269 -0
  138. package/dist/scanners/detection/engines/data-flow.js.map +1 -0
  139. package/dist/scanners/detection/index.d.ts +29 -0
  140. package/dist/scanners/detection/index.d.ts.map +1 -0
  141. package/dist/scanners/detection/index.js +140 -0
  142. package/dist/scanners/detection/index.js.map +1 -0
  143. package/dist/scanners/detection/rules/builtin.d.ts +14 -0
  144. package/dist/scanners/detection/rules/builtin.d.ts.map +1 -0
  145. package/dist/scanners/detection/rules/builtin.js +307 -0
  146. package/dist/scanners/detection/rules/builtin.js.map +1 -0
  147. package/dist/scanners/detection/rules/loader.d.ts +19 -0
  148. package/dist/scanners/detection/rules/loader.d.ts.map +1 -0
  149. package/dist/scanners/detection/rules/loader.js +111 -0
  150. package/dist/scanners/detection/rules/loader.js.map +1 -0
  151. package/dist/scanners/detection/types.d.ts +171 -0
  152. package/dist/scanners/detection/types.d.ts.map +1 -0
  153. package/dist/scanners/detection/types.js +36 -0
  154. package/dist/scanners/detection/types.js.map +1 -0
  155. package/dist/scanners/eslint.d.ts.map +1 -1
  156. package/dist/scanners/eslint.js +45 -3
  157. package/dist/scanners/eslint.js.map +1 -1
  158. package/dist/scanners/index.d.ts +9 -1
  159. package/dist/scanners/index.d.ts.map +1 -1
  160. package/dist/scanners/index.js +64 -0
  161. package/dist/scanners/index.js.map +1 -1
  162. package/dist/scanners/index.test.js +6 -6
  163. package/dist/scanners/index.test.js.map +1 -1
  164. package/dist/scanners/scale/bottleneck-detector.d.ts +13 -2
  165. package/dist/scanners/scale/bottleneck-detector.d.ts.map +1 -1
  166. package/dist/scanners/scale/bottleneck-detector.js +199 -72
  167. package/dist/scanners/scale/bottleneck-detector.js.map +1 -1
  168. package/dist/scanners/scale/types.d.ts +3 -3
  169. package/dist/scanners/types.d.ts +19 -2
  170. package/dist/scanners/types.d.ts.map +1 -1
  171. package/dist/scanners/types.js +1 -0
  172. package/dist/scanners/types.js.map +1 -1
  173. package/dist/scanners/typescript.d.ts.map +1 -1
  174. package/dist/scanners/typescript.js +36 -4
  175. package/dist/scanners/typescript.js.map +1 -1
  176. package/package.json +5 -1
@@ -0,0 +1,249 @@
1
+ /**
2
+ * JSON Fallback Storage Tests
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
5
+ import { mkdtemp, rm, readFile } from "fs/promises";
6
+ import { existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { tmpdir } from "os";
9
+ import { JsonFallbackStorage } from "../json-fallback.js";
10
+ describe("JSON Fallback Storage", () => {
11
+ let tempDir;
12
+ let storage;
13
+ beforeEach(async () => {
14
+ tempDir = await mkdtemp(join(tmpdir(), "fallback-test-"));
15
+ storage = await JsonFallbackStorage.create(tempDir);
16
+ });
17
+ afterEach(async () => {
18
+ storage.close();
19
+ await rm(tempDir, { recursive: true, force: true });
20
+ });
21
+ describe("Projects", () => {
22
+ it("creates and retrieves a project", () => {
23
+ const project = storage.findOrCreateProject("/test/project");
24
+ expect(project.id).toBeDefined();
25
+ expect(project.path).toBe("/test/project");
26
+ expect(project.name).toBe("project");
27
+ });
28
+ it("returns existing project on second call", () => {
29
+ const first = storage.findOrCreateProject("/test/project");
30
+ const second = storage.findOrCreateProject("/test/project");
31
+ expect(first.id).toBe(second.id);
32
+ });
33
+ it("finds project by path", () => {
34
+ storage.findOrCreateProject("/test/project");
35
+ const found = storage.findProjectByPath("/test/project");
36
+ expect(found).toBeDefined();
37
+ expect(found?.path).toBe("/test/project");
38
+ });
39
+ });
40
+ describe("Findings", () => {
41
+ it("creates and retrieves a finding", () => {
42
+ const project = storage.findOrCreateProject("/test");
43
+ const finding = storage.createFinding({
44
+ projectId: project.id,
45
+ severity: "high",
46
+ category: "sql-injection",
47
+ file: "src/api.ts",
48
+ line: 42,
49
+ description: "SQL injection",
50
+ scannerSource: "semgrep",
51
+ confidence: 95,
52
+ status: "open",
53
+ });
54
+ expect(finding.id).toBeDefined();
55
+ expect(finding.severity).toBe("high");
56
+ expect(finding.firstSeenAt).toBeDefined();
57
+ });
58
+ it("upserts findings correctly", () => {
59
+ const project = storage.findOrCreateProject("/test");
60
+ const { finding: first, isNew: isNew1 } = storage.upsertFinding({
61
+ projectId: project.id,
62
+ severity: "high",
63
+ category: "sql-injection",
64
+ file: "src/api.ts",
65
+ line: 42,
66
+ description: "SQL injection",
67
+ scannerSource: "semgrep",
68
+ confidence: 95,
69
+ });
70
+ const { finding: second, isNew: isNew2 } = storage.upsertFinding({
71
+ projectId: project.id,
72
+ severity: "high",
73
+ category: "sql-injection",
74
+ file: "src/api.ts",
75
+ line: 42,
76
+ description: "SQL injection",
77
+ scannerSource: "semgrep",
78
+ confidence: 95,
79
+ });
80
+ expect(isNew1).toBe(true);
81
+ expect(isNew2).toBe(false);
82
+ expect(first.id).toBe(second.id);
83
+ });
84
+ it("filters findings correctly", () => {
85
+ const project = storage.findOrCreateProject("/test");
86
+ storage.createFinding({
87
+ projectId: project.id,
88
+ severity: "high",
89
+ category: "sql-injection",
90
+ file: "src/api.ts",
91
+ line: 42,
92
+ description: "SQL injection",
93
+ scannerSource: "semgrep",
94
+ confidence: 95,
95
+ status: "open",
96
+ });
97
+ storage.createFinding({
98
+ projectId: project.id,
99
+ severity: "low",
100
+ category: "style",
101
+ file: "src/utils.ts",
102
+ line: 10,
103
+ description: "Style issue",
104
+ scannerSource: "eslint",
105
+ confidence: 100,
106
+ status: "open",
107
+ });
108
+ const highSeverity = storage.findFindings({ severity: "high" });
109
+ expect(highSeverity).toHaveLength(1);
110
+ const allOpen = storage.findFindings({ status: "open" });
111
+ expect(allOpen).toHaveLength(2);
112
+ });
113
+ it("marks findings as fixed", () => {
114
+ const project = storage.findOrCreateProject("/test");
115
+ const finding = storage.createFinding({
116
+ projectId: project.id,
117
+ severity: "high",
118
+ category: "test",
119
+ file: "test.ts",
120
+ line: 1,
121
+ description: "Test",
122
+ scannerSource: "semgrep",
123
+ confidence: 100,
124
+ status: "open",
125
+ });
126
+ const count = storage.markFixed([finding.id], "user@test.com");
127
+ expect(count).toBe(1);
128
+ const updated = storage.findById(finding.id);
129
+ expect(updated?.status).toBe("fixed");
130
+ expect(updated?.fixedBy).toBe("user@test.com");
131
+ });
132
+ it("marks findings as false positive", () => {
133
+ const project = storage.findOrCreateProject("/test");
134
+ const finding = storage.createFinding({
135
+ projectId: project.id,
136
+ severity: "high",
137
+ category: "test",
138
+ file: "test.ts",
139
+ line: 1,
140
+ description: "Test",
141
+ scannerSource: "semgrep",
142
+ confidence: 100,
143
+ status: "open",
144
+ });
145
+ const result = storage.markFalsePositive(finding.id, "Not a real issue");
146
+ expect(result).toBe(true);
147
+ const updated = storage.findById(finding.id);
148
+ expect(updated?.status).toBe("false_positive");
149
+ expect(updated?.falsePositive).toBe(true);
150
+ expect(updated?.falsePositiveReason).toBe("Not a real issue");
151
+ });
152
+ });
153
+ describe("Stats", () => {
154
+ it("calculates project stats", () => {
155
+ const project = storage.findOrCreateProject("/test");
156
+ storage.createFinding({
157
+ projectId: project.id,
158
+ severity: "critical",
159
+ category: "sql-injection",
160
+ file: "src/api.ts",
161
+ line: 42,
162
+ description: "SQL injection",
163
+ scannerSource: "semgrep",
164
+ confidence: 95,
165
+ status: "open",
166
+ });
167
+ storage.createFinding({
168
+ projectId: project.id,
169
+ severity: "high",
170
+ category: "xss",
171
+ file: "src/view.ts",
172
+ line: 10,
173
+ description: "XSS",
174
+ scannerSource: "semgrep",
175
+ confidence: 90,
176
+ status: "open",
177
+ });
178
+ const stats = storage.getStats(project.id);
179
+ expect(stats.totalFindings).toBe(2);
180
+ expect(stats.openFindings).toBe(2);
181
+ expect(stats.bySeverity.critical).toBe(1);
182
+ expect(stats.bySeverity.high).toBe(1);
183
+ });
184
+ });
185
+ describe("Persistence", () => {
186
+ it("saves and loads data", async () => {
187
+ const project = storage.findOrCreateProject("/test");
188
+ storage.createFinding({
189
+ projectId: project.id,
190
+ severity: "high",
191
+ category: "test",
192
+ file: "test.ts",
193
+ line: 1,
194
+ description: "Test finding",
195
+ scannerSource: "semgrep",
196
+ confidence: 100,
197
+ status: "open",
198
+ });
199
+ await storage.save();
200
+ const filePath = join(tempDir, ".vaspera", "findings-fallback.json");
201
+ expect(existsSync(filePath)).toBe(true);
202
+ const content = await readFile(filePath, "utf-8");
203
+ const data = JSON.parse(content);
204
+ expect(data.version).toBe(1);
205
+ expect(Object.keys(data.projects)).toHaveLength(1);
206
+ expect(Object.keys(data.findings)).toHaveLength(1);
207
+ });
208
+ it("loads existing data on create", async () => {
209
+ const project = storage.findOrCreateProject("/test");
210
+ storage.createFinding({
211
+ projectId: project.id,
212
+ severity: "high",
213
+ category: "test",
214
+ file: "test.ts",
215
+ line: 1,
216
+ description: "Test finding",
217
+ scannerSource: "semgrep",
218
+ confidence: 100,
219
+ status: "open",
220
+ });
221
+ await storage.save();
222
+ storage.close();
223
+ const newStorage = await JsonFallbackStorage.create(tempDir);
224
+ const findings = newStorage.findFindings({ status: "open" });
225
+ expect(findings).toHaveLength(1);
226
+ expect(findings[0].description).toBe("Test finding");
227
+ newStorage.close();
228
+ });
229
+ });
230
+ describe("Scans", () => {
231
+ it("creates and completes scans", () => {
232
+ const project = storage.findOrCreateProject("/test");
233
+ const scan = storage.createScan(project.id, "cert-001");
234
+ expect(scan.id).toBeDefined();
235
+ expect(scan.status).toBe("running");
236
+ storage.completeScan(scan.id, {
237
+ totalFindings: 5,
238
+ newFindings: 3,
239
+ fixedFindings: 0,
240
+ bySeverity: { critical: 1, high: 2, medium: 2, low: 0, info: 0 },
241
+ byScanner: { semgrep: 5 },
242
+ duration: 1000,
243
+ });
244
+ const stats = storage.getStats(project.id);
245
+ expect(stats.lastScanAt).toBeDefined();
246
+ });
247
+ });
248
+ });
249
+ //# sourceMappingURL=json-fallback.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-fallback.test.js","sourceRoot":"","sources":["../../../src/persistence/__tests__/json-fallback.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Persistence Layer Tests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=persistence.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.test.d.ts","sourceRoot":"","sources":["../../../src/persistence/__tests__/persistence.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Persistence Layer Tests
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
5
+ import { mkdtemp, rm } from "fs/promises";
6
+ import { join } from "path";
7
+ import { tmpdir } from "os";
8
+ import { PersistenceManager } from "../index.js";
9
+ describe("Persistence Layer", () => {
10
+ let manager;
11
+ let tempDir;
12
+ beforeEach(async () => {
13
+ tempDir = await mkdtemp(join(tmpdir(), `persistence-test-${Math.random().toString(36).slice(2, 8)}-`));
14
+ manager = PersistenceManager.createInMemory();
15
+ });
16
+ afterEach(async () => {
17
+ manager.close();
18
+ await rm(tempDir, { recursive: true, force: true });
19
+ });
20
+ describe("Projects Repository", () => {
21
+ it("creates a project", () => {
22
+ const project = manager.projects.create("/test/project", "Test Project");
23
+ expect(project.id).toBeDefined();
24
+ expect(project.path).toBe("/test/project");
25
+ expect(project.name).toBe("Test Project");
26
+ expect(project.createdAt).toBeDefined();
27
+ });
28
+ it("finds project by path", () => {
29
+ manager.projects.create("/test/project", "Test Project");
30
+ const found = manager.projects.findByPath("/test/project");
31
+ expect(found).toBeDefined();
32
+ expect(found?.name).toBe("Test Project");
33
+ });
34
+ it("finds or creates project", () => {
35
+ const first = manager.projects.findOrCreate("/test/project", "Test");
36
+ const second = manager.projects.findOrCreate("/test/project", "Different");
37
+ expect(first.id).toBe(second.id);
38
+ });
39
+ it("lists all projects", () => {
40
+ manager.projects.create("/project1");
41
+ manager.projects.create("/project2");
42
+ const projects = manager.projects.list();
43
+ expect(projects.length).toBe(2);
44
+ });
45
+ });
46
+ describe("Findings Repository", () => {
47
+ let projectId;
48
+ beforeEach(() => {
49
+ const project = manager.projects.create("/test/project");
50
+ projectId = project.id;
51
+ });
52
+ it("creates a finding", () => {
53
+ const finding = manager.findings.create({
54
+ projectId,
55
+ severity: "high",
56
+ category: "sql-injection",
57
+ file: "src/api.ts",
58
+ line: 42,
59
+ description: "SQL injection vulnerability",
60
+ scannerSource: "semgrep",
61
+ status: "open",
62
+ confidence: 95,
63
+ });
64
+ expect(finding.id).toBeDefined();
65
+ expect(finding.severity).toBe("high");
66
+ expect(finding.firstSeenAt).toBeDefined();
67
+ });
68
+ it("upserts finding from scan", () => {
69
+ const first = manager.findings.upsertFromScan({
70
+ projectId,
71
+ severity: "high",
72
+ category: "xss",
73
+ file: "src/ui.ts",
74
+ line: 100,
75
+ description: "XSS vulnerability",
76
+ scannerSource: "semgrep",
77
+ confidence: 90,
78
+ });
79
+ expect(first.isNew).toBe(true);
80
+ const second = manager.findings.upsertFromScan({
81
+ projectId,
82
+ severity: "high",
83
+ category: "xss",
84
+ file: "src/ui.ts",
85
+ line: 100,
86
+ description: "XSS vulnerability",
87
+ scannerSource: "semgrep",
88
+ confidence: 90,
89
+ });
90
+ expect(second.isNew).toBe(false);
91
+ expect(second.finding.id).toBe(first.finding.id);
92
+ });
93
+ it("filters findings", () => {
94
+ manager.findings.create({
95
+ projectId,
96
+ severity: "critical",
97
+ category: "sql-injection",
98
+ file: "src/api.ts",
99
+ line: 10,
100
+ description: "Critical SQL injection",
101
+ scannerSource: "semgrep",
102
+ status: "open",
103
+ confidence: 100,
104
+ });
105
+ manager.findings.create({
106
+ projectId,
107
+ severity: "low",
108
+ category: "code-smell",
109
+ file: "src/utils.ts",
110
+ line: 20,
111
+ description: "Minor issue",
112
+ scannerSource: "eslint",
113
+ status: "open",
114
+ confidence: 80,
115
+ });
116
+ const critical = manager.findings.find({ projectId, severity: "critical" });
117
+ expect(critical.length).toBe(1);
118
+ const semgrep = manager.findings.find({ projectId, scannerSource: "semgrep" });
119
+ expect(semgrep.length).toBe(1);
120
+ });
121
+ it("updates finding status", () => {
122
+ const finding = manager.findings.create({
123
+ projectId,
124
+ severity: "high",
125
+ category: "xss",
126
+ file: "src/ui.ts",
127
+ line: 50,
128
+ description: "XSS",
129
+ scannerSource: "semgrep",
130
+ status: "open",
131
+ confidence: 90,
132
+ });
133
+ const updated = manager.findings.updateStatus(finding.id, "fixed", "developer");
134
+ expect(updated).toBe(true);
135
+ const fetched = manager.findings.findById(finding.id);
136
+ expect(fetched?.status).toBe("fixed");
137
+ expect(fetched?.fixedAt).toBeDefined();
138
+ });
139
+ it("marks finding as false positive", () => {
140
+ const finding = manager.findings.create({
141
+ projectId,
142
+ severity: "medium",
143
+ category: "security",
144
+ file: "src/test.ts",
145
+ line: 1,
146
+ description: "Test finding",
147
+ scannerSource: "semgrep",
148
+ status: "open",
149
+ confidence: 70,
150
+ });
151
+ manager.findings.markFalsePositive(finding.id, "Not applicable in this context");
152
+ const fetched = manager.findings.findById(finding.id);
153
+ expect(fetched?.status).toBe("false_positive");
154
+ expect(fetched?.falsePositive).toBe(true);
155
+ expect(fetched?.falsePositiveReason).toBe("Not applicable in this context");
156
+ });
157
+ it("gets project stats", () => {
158
+ manager.findings.create({
159
+ projectId,
160
+ severity: "critical",
161
+ category: "sql-injection",
162
+ file: "src/api.ts",
163
+ line: 10,
164
+ description: "Critical",
165
+ scannerSource: "semgrep",
166
+ status: "open",
167
+ confidence: 100,
168
+ });
169
+ manager.findings.create({
170
+ projectId,
171
+ severity: "high",
172
+ category: "xss",
173
+ file: "src/ui.ts",
174
+ line: 20,
175
+ description: "High",
176
+ scannerSource: "semgrep",
177
+ status: "open",
178
+ confidence: 90,
179
+ });
180
+ const stats = manager.findings.getStats(projectId);
181
+ expect(stats.totalFindings).toBe(2);
182
+ expect(stats.openFindings).toBe(2);
183
+ expect(stats.bySeverity.critical).toBe(1);
184
+ expect(stats.bySeverity.high).toBe(1);
185
+ });
186
+ });
187
+ describe("Scans Repository", () => {
188
+ let projectId;
189
+ beforeEach(() => {
190
+ const project = manager.projects.create("/test/project");
191
+ projectId = project.id;
192
+ });
193
+ it("creates a scan", () => {
194
+ const scan = manager.scans.create(projectId);
195
+ expect(scan.id).toBeDefined();
196
+ expect(scan.projectId).toBe(projectId);
197
+ expect(scan.status).toBe("running");
198
+ });
199
+ it("completes a scan", () => {
200
+ const scan = manager.scans.create(projectId);
201
+ manager.scans.complete(scan.id, {
202
+ totalFindings: 5,
203
+ newFindings: 3,
204
+ fixedFindings: 1,
205
+ bySeverity: { critical: 1, high: 2, medium: 2, low: 0, info: 0 },
206
+ byScanner: { semgrep: 4, gitleaks: 1 },
207
+ duration: 5000,
208
+ });
209
+ const fetched = manager.scans.findById(scan.id);
210
+ expect(fetched?.status).toBe("completed");
211
+ expect(fetched?.totalFindings).toBe(5);
212
+ expect(fetched?.completedAt).toBeDefined();
213
+ });
214
+ it("fails a scan", () => {
215
+ const scan = manager.scans.create(projectId);
216
+ manager.scans.fail(scan.id, "Scanner crashed");
217
+ const fetched = manager.scans.findById(scan.id);
218
+ expect(fetched?.status).toBe("failed");
219
+ expect(fetched?.error).toBe("Scanner crashed");
220
+ });
221
+ it("lists project scans", () => {
222
+ manager.scans.create(projectId);
223
+ manager.scans.create(projectId);
224
+ manager.scans.create(projectId);
225
+ const scans = manager.scans.findByProject(projectId);
226
+ expect(scans.length).toBe(3);
227
+ });
228
+ });
229
+ describe("Trends Repository", () => {
230
+ let projectId;
231
+ beforeEach(() => {
232
+ const project = manager.projects.create("/test/project");
233
+ projectId = project.id;
234
+ manager.findings.create({
235
+ projectId,
236
+ severity: "high",
237
+ category: "xss",
238
+ file: "src/ui.ts",
239
+ line: 10,
240
+ description: "XSS",
241
+ scannerSource: "semgrep",
242
+ status: "open",
243
+ confidence: 90,
244
+ });
245
+ });
246
+ it("calculates trends", () => {
247
+ const trends = manager.trends.calculateTrends(projectId, "day", 7);
248
+ expect(Array.isArray(trends)).toBe(true);
249
+ });
250
+ it("gets category breakdown", () => {
251
+ const breakdown = manager.trends.getCategoryBreakdown(projectId);
252
+ expect(breakdown.length).toBeGreaterThan(0);
253
+ expect(breakdown[0]).toHaveProperty("category");
254
+ expect(breakdown[0]).toHaveProperty("count");
255
+ expect(breakdown[0]).toHaveProperty("percentage");
256
+ });
257
+ it("gets scanner effectiveness", () => {
258
+ const effectiveness = manager.trends.getScannerEffectiveness(projectId);
259
+ expect(effectiveness.length).toBeGreaterThan(0);
260
+ expect(effectiveness[0]).toHaveProperty("scanner");
261
+ expect(effectiveness[0]).toHaveProperty("found");
262
+ expect(effectiveness[0]).toHaveProperty("accuracy");
263
+ });
264
+ });
265
+ describe("End-to-End Workflow", () => {
266
+ it("complete scan workflow with in-memory database", () => {
267
+ const localManager = PersistenceManager.createInMemory();
268
+ const project = localManager.projects.create("/test/project", "Test Project");
269
+ const scan = localManager.scans.create(project.id);
270
+ const { finding: finding1, isNew: isNew1 } = localManager.findings.upsertFromScan({
271
+ projectId: project.id,
272
+ severity: "high",
273
+ category: "sql-injection",
274
+ file: "src/api.ts",
275
+ line: 42,
276
+ description: "SQL injection",
277
+ scannerSource: "semgrep",
278
+ confidence: 95,
279
+ });
280
+ const { finding: finding2, isNew: isNew2 } = localManager.findings.upsertFromScan({
281
+ projectId: project.id,
282
+ severity: "medium",
283
+ category: "xss",
284
+ file: "src/ui.ts",
285
+ line: 100,
286
+ description: "XSS",
287
+ scannerSource: "semgrep",
288
+ confidence: 85,
289
+ });
290
+ expect(isNew1).toBe(true);
291
+ expect(isNew2).toBe(true);
292
+ localManager.scans.complete(scan.id, {
293
+ totalFindings: 2,
294
+ newFindings: 2,
295
+ fixedFindings: 0,
296
+ bySeverity: { critical: 0, high: 1, medium: 1, low: 0, info: 0 },
297
+ byScanner: { semgrep: 2 },
298
+ duration: 1000,
299
+ });
300
+ const completedScan = localManager.scans.findById(scan.id);
301
+ expect(completedScan?.status).toBe("completed");
302
+ expect(completedScan?.totalFindings).toBe(2);
303
+ const openFindings = localManager.findings.find({ projectId: project.id, status: "open" });
304
+ expect(openFindings.length).toBe(2);
305
+ localManager.findings.updateStatus(finding1.id, "fixed", "developer");
306
+ const afterFix = localManager.findings.find({ projectId: project.id, status: "open" });
307
+ expect(afterFix.length).toBe(1);
308
+ const stats = localManager.findings.getStats(project.id);
309
+ expect(stats.openFindings).toBe(1);
310
+ expect(stats.fixedFindings).toBe(1);
311
+ localManager.close();
312
+ });
313
+ it("tracks finding deduplication across scans", () => {
314
+ const localManager = PersistenceManager.createInMemory();
315
+ const project = localManager.projects.create("/test/project2");
316
+ const first = localManager.findings.upsertFromScan({
317
+ projectId: project.id,
318
+ severity: "critical",
319
+ category: "secrets",
320
+ file: "src/config.ts",
321
+ line: 5,
322
+ description: "Hardcoded secret",
323
+ scannerSource: "gitleaks",
324
+ confidence: 100,
325
+ });
326
+ expect(first.isNew).toBe(true);
327
+ const second = localManager.findings.upsertFromScan({
328
+ projectId: project.id,
329
+ severity: "critical",
330
+ category: "secrets",
331
+ file: "src/config.ts",
332
+ line: 5,
333
+ description: "Hardcoded secret",
334
+ scannerSource: "gitleaks",
335
+ confidence: 100,
336
+ });
337
+ expect(second.isNew).toBe(false);
338
+ expect(second.finding.id).toBe(first.finding.id);
339
+ const allFindings = localManager.findings.find({ projectId: project.id });
340
+ expect(allFindings.length).toBe(1);
341
+ localManager.close();
342
+ });
343
+ it("calculates project trends", () => {
344
+ const localManager = PersistenceManager.createInMemory();
345
+ const project = localManager.projects.create("/test/project3");
346
+ for (let i = 0; i < 5; i++) {
347
+ localManager.findings.create({
348
+ projectId: project.id,
349
+ severity: i < 2 ? "critical" : "high",
350
+ category: "security",
351
+ file: `src/file${i}.ts`,
352
+ line: i * 10,
353
+ description: `Finding ${i}`,
354
+ scannerSource: "semgrep",
355
+ status: "open",
356
+ confidence: 90,
357
+ });
358
+ }
359
+ const trends = localManager.trends.calculateTrends(project.id, "day", 7);
360
+ expect(Array.isArray(trends)).toBe(true);
361
+ const breakdown = localManager.trends.getCategoryBreakdown(project.id);
362
+ expect(breakdown.length).toBeGreaterThan(0);
363
+ expect(breakdown[0].category).toBe("security");
364
+ expect(breakdown[0].count).toBe(5);
365
+ localManager.close();
366
+ });
367
+ });
368
+ });
369
+ //# sourceMappingURL=persistence.test.js.map