@vltpkg/query 1.0.0-rc.32 → 1.0.0-rc.33

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 (203) hide show
  1. package/dist/pseudo/hostname.js +3 -1
  2. package/dist/pseudo/scripts.js +1 -1
  3. package/package.json +19 -10
  4. package/skills/dss-query/REFERENCE.md +119 -0
  5. package/skills/dss-query/SKILL.md +190 -0
  6. package/skills/dss-query/evals/README.md +79 -0
  7. package/skills/dss-query/evals/evals.json +89 -0
  8. package/skills/dss-query/evals/grade.mjs +480 -0
  9. package/src/attribute.ts +186 -0
  10. package/src/combinator.ts +132 -0
  11. package/src/id.ts +46 -0
  12. package/src/index.ts +613 -0
  13. package/src/parser.ts +121 -0
  14. package/src/pseudo/abandoned.ts +9 -0
  15. package/src/pseudo/attr.ts +92 -0
  16. package/src/pseudo/built.ts +19 -0
  17. package/src/pseudo/confused.ts +29 -0
  18. package/src/pseudo/cve.ts +93 -0
  19. package/src/pseudo/cwe.ts +97 -0
  20. package/src/pseudo/debug.ts +9 -0
  21. package/src/pseudo/deprecated.ts +9 -0
  22. package/src/pseudo/dev.ts +18 -0
  23. package/src/pseudo/diff.ts +90 -0
  24. package/src/pseudo/dist.ts +144 -0
  25. package/src/pseudo/dynamic.ts +9 -0
  26. package/src/pseudo/empty.ts +15 -0
  27. package/src/pseudo/entropic.ts +9 -0
  28. package/src/pseudo/env.ts +6 -0
  29. package/src/pseudo/eval.ts +9 -0
  30. package/src/pseudo/fs.ts +9 -0
  31. package/src/pseudo/helpers.ts +106 -0
  32. package/src/pseudo/host.ts +111 -0
  33. package/src/pseudo/hostname.ts +156 -0
  34. package/src/pseudo/license.ts +134 -0
  35. package/src/pseudo/link.ts +30 -0
  36. package/src/pseudo/malware.ts +249 -0
  37. package/src/pseudo/minified.ts +9 -0
  38. package/src/pseudo/missing.ts +16 -0
  39. package/src/pseudo/native.ts +9 -0
  40. package/src/pseudo/network.ts +9 -0
  41. package/src/pseudo/obfuscated.ts +9 -0
  42. package/src/pseudo/optional.ts +18 -0
  43. package/src/pseudo/outdated.ts +305 -0
  44. package/src/pseudo/overridden.ts +20 -0
  45. package/src/pseudo/path.ts +146 -0
  46. package/src/pseudo/peer.ts +18 -0
  47. package/src/pseudo/prerelease.ts +47 -0
  48. package/src/pseudo/private.ts +19 -0
  49. package/src/pseudo/prod.ts +18 -0
  50. package/src/pseudo/published.ts +248 -0
  51. package/src/pseudo/registry.ts +29 -0
  52. package/src/pseudo/root.ts +19 -0
  53. package/src/pseudo/scanned.ts +20 -0
  54. package/src/pseudo/score.ts +186 -0
  55. package/src/pseudo/scripts.ts +54 -0
  56. package/src/pseudo/semver.ts +320 -0
  57. package/src/pseudo/severity.ts +231 -0
  58. package/src/pseudo/shell.ts +9 -0
  59. package/src/pseudo/shrinkwrap.ts +9 -0
  60. package/src/pseudo/spec.ts +131 -0
  61. package/src/pseudo/squat.ts +225 -0
  62. package/src/pseudo/suspicious.ts +9 -0
  63. package/src/pseudo/tracker.ts +9 -0
  64. package/src/pseudo/trivial.ts +9 -0
  65. package/src/pseudo/type.ts +26 -0
  66. package/src/pseudo/undesirable.ts +9 -0
  67. package/src/pseudo/unknown.ts +9 -0
  68. package/src/pseudo/unmaintained.ts +9 -0
  69. package/src/pseudo/unpopular.ts +9 -0
  70. package/src/pseudo/unstable.ts +9 -0
  71. package/src/pseudo/vulnerable.ts +26 -0
  72. package/src/pseudo/workspace.ts +22 -0
  73. package/src/pseudo.ts +413 -0
  74. package/src/types.ts +164 -0
  75. package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
  76. package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
  77. package/tap-snapshots/test/id.ts.test.cjs +118 -0
  78. package/tap-snapshots/test/parser.ts.test.cjs +344 -0
  79. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
  80. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
  81. package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
  82. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
  83. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
  84. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
  85. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
  86. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
  87. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
  88. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
  89. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
  90. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
  91. package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
  92. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
  93. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
  94. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
  95. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
  96. package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
  97. package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
  98. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +191 -0
  99. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
  100. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
  101. package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
  102. package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
  103. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
  104. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
  105. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
  106. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
  107. package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
  108. package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
  109. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
  110. package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
  111. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
  112. package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
  113. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
  114. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
  115. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
  116. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
  117. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
  118. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
  119. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
  120. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
  121. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
  122. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
  123. package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
  124. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
  125. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
  126. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
  127. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
  128. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
  129. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +18 -0
  130. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
  131. package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
  132. package/test/attribute.ts +266 -0
  133. package/test/combinator.ts +168 -0
  134. package/test/fixtures/graph.ts +999 -0
  135. package/test/fixtures/selector.ts +103 -0
  136. package/test/fixtures/types.ts +7 -0
  137. package/test/id.ts +102 -0
  138. package/test/index.ts +992 -0
  139. package/test/parser.ts +117 -0
  140. package/test/pseudo/abandoned.ts +98 -0
  141. package/test/pseudo/attr.ts +298 -0
  142. package/test/pseudo/built.ts +214 -0
  143. package/test/pseudo/confused.ts +160 -0
  144. package/test/pseudo/cve.ts +249 -0
  145. package/test/pseudo/cwe.ts +255 -0
  146. package/test/pseudo/debug.ts +98 -0
  147. package/test/pseudo/deprecated.ts +98 -0
  148. package/test/pseudo/dev.ts +61 -0
  149. package/test/pseudo/diff.ts +417 -0
  150. package/test/pseudo/dist.ts +215 -0
  151. package/test/pseudo/dynamic.ts +98 -0
  152. package/test/pseudo/empty.ts +108 -0
  153. package/test/pseudo/entropic.ts +101 -0
  154. package/test/pseudo/env.ts +98 -0
  155. package/test/pseudo/eval.ts +98 -0
  156. package/test/pseudo/fs.ts +98 -0
  157. package/test/pseudo/helpers.ts +370 -0
  158. package/test/pseudo/host-context.ts +276 -0
  159. package/test/pseudo/hostname.ts +314 -0
  160. package/test/pseudo/license.ts +265 -0
  161. package/test/pseudo/link.ts +94 -0
  162. package/test/pseudo/malware.ts +431 -0
  163. package/test/pseudo/minified.ts +98 -0
  164. package/test/pseudo/missing.ts +111 -0
  165. package/test/pseudo/native.ts +98 -0
  166. package/test/pseudo/network.ts +98 -0
  167. package/test/pseudo/obfuscated.ts +98 -0
  168. package/test/pseudo/optional.ts +73 -0
  169. package/test/pseudo/outdated.ts +331 -0
  170. package/test/pseudo/overridden.ts +317 -0
  171. package/test/pseudo/path.ts +680 -0
  172. package/test/pseudo/peer.ts +101 -0
  173. package/test/pseudo/prerelease.ts +279 -0
  174. package/test/pseudo/private.ts +108 -0
  175. package/test/pseudo/prod.ts +61 -0
  176. package/test/pseudo/published.ts +557 -0
  177. package/test/pseudo/registry.ts +122 -0
  178. package/test/pseudo/root.ts +66 -0
  179. package/test/pseudo/scanned.ts +78 -0
  180. package/test/pseudo/score.ts +591 -0
  181. package/test/pseudo/scripts.ts +294 -0
  182. package/test/pseudo/semver.ts +822 -0
  183. package/test/pseudo/severity.ts +310 -0
  184. package/test/pseudo/shell.ts +98 -0
  185. package/test/pseudo/shrinkwrap.ts +98 -0
  186. package/test/pseudo/spec.ts +525 -0
  187. package/test/pseudo/squat.ts +565 -0
  188. package/test/pseudo/suspicious.ts +98 -0
  189. package/test/pseudo/tracker.ts +98 -0
  190. package/test/pseudo/trivial.ts +98 -0
  191. package/test/pseudo/type.ts +105 -0
  192. package/test/pseudo/undesirable.ts +98 -0
  193. package/test/pseudo/unknown.ts +98 -0
  194. package/test/pseudo/unmaintained.ts +98 -0
  195. package/test/pseudo/unpopular.ts +98 -0
  196. package/test/pseudo/unstable.ts +98 -0
  197. package/test/pseudo/vulnerable.ts +130 -0
  198. package/test/pseudo/workspace.ts +111 -0
  199. package/test/pseudo.ts +558 -0
  200. package/tsconfig.json +3 -0
  201. package/tsconfig.publish.json +19 -0
  202. package/tsconfig.publish.tsbuildinfo +1 -0
  203. package/typedoc.mjs +2 -0
@@ -0,0 +1,431 @@
1
+ import t from 'tap'
2
+ import { joinDepIDTuple } from '@vltpkg/dep-id'
3
+ import { parse, asPostcssNodeWithChildren } from '@vltpkg/dss-parser'
4
+ import { asSecurityArchiveLike } from '@vltpkg/security-archive'
5
+ import { getSimpleGraph } from '../fixtures/graph.ts'
6
+ import {
7
+ malware,
8
+ isMalwareKind,
9
+ asMalwareKind,
10
+ parseInternals,
11
+ } from '../../src/pseudo/malware.ts'
12
+ import type { ParserState } from '../../src/types.ts'
13
+
14
+ t.test('selects packages with a specific malware kind', async t => {
15
+ const getState = (query: string, graph = getSimpleGraph()) => {
16
+ const ast = parse(query)
17
+ const current = ast.first.first
18
+ const state: ParserState = {
19
+ comment: '',
20
+ current,
21
+ initial: {
22
+ edges: new Set(graph.edges.values()),
23
+ nodes: new Set(graph.nodes.values()),
24
+ },
25
+ partial: {
26
+ edges: new Set(graph.edges.values()),
27
+ nodes: new Set(graph.nodes.values()),
28
+ },
29
+ collect: {
30
+ edges: new Set(),
31
+ nodes: new Set(),
32
+ },
33
+ cancellable: async () => {},
34
+ walk: async i => i,
35
+ securityArchive: asSecurityArchiveLike(
36
+ new Map([
37
+ [
38
+ joinDepIDTuple(['registry', '', 'a@1.0.0']),
39
+ {
40
+ id: joinDepIDTuple(['registry', '', 'a@1.0.0']),
41
+ alerts: [{ type: 'malware' }],
42
+ },
43
+ ],
44
+ [
45
+ joinDepIDTuple(['registry', '', 'b@1.0.0']),
46
+ {
47
+ id: joinDepIDTuple(['registry', '', 'b@1.0.0']),
48
+ alerts: [{ type: 'gptMalware' }],
49
+ },
50
+ ],
51
+ [
52
+ joinDepIDTuple(['registry', '', 'c@1.0.0']),
53
+ {
54
+ id: joinDepIDTuple(['registry', '', 'c@1.0.0']),
55
+ alerts: [{ type: 'gptSecurity' }],
56
+ },
57
+ ],
58
+ [
59
+ joinDepIDTuple(['registry', '', 'd@1.0.0']),
60
+ {
61
+ id: joinDepIDTuple(['registry', '', 'd@1.0.0']),
62
+ alerts: [{ type: 'gptAnomaly' }],
63
+ },
64
+ ],
65
+ [
66
+ joinDepIDTuple(['registry', '', 'e@1.0.0']),
67
+ {
68
+ id: joinDepIDTuple(['registry', '', 'e@1.0.0']),
69
+ alerts: [{ type: 'malware' }],
70
+ },
71
+ ],
72
+ [
73
+ joinDepIDTuple(['registry', '', 'f@1.0.0']),
74
+ {
75
+ id: joinDepIDTuple(['registry', '', 'f@1.0.0']),
76
+ alerts: [{ type: 'gptMalware' }],
77
+ },
78
+ ],
79
+ ]),
80
+ ),
81
+ importers: new Set(graph.importers),
82
+ retries: 0,
83
+ signal: new AbortController().signal,
84
+ specificity: { idCounter: 0, commonCounter: 0 },
85
+ }
86
+ return state
87
+ }
88
+
89
+ await t.test(
90
+ 'filter out any node that does not have the malware alert',
91
+ async t => {
92
+ const res = await malware(getState(':malware("critical")'))
93
+ t.strictSame(
94
+ [...res.partial.nodes].map(n => n.name),
95
+ ['a', 'e'],
96
+ 'should select only packages with the specified malware alert',
97
+ )
98
+ t.matchSnapshot({
99
+ nodes: [...res.partial.nodes].map(n => n.name),
100
+ edges: [...res.partial.edges].map(e => e.name),
101
+ })
102
+ },
103
+ )
104
+
105
+ await t.test('filter using numbered param', async t => {
106
+ const res = await malware(getState(':malware(0)'))
107
+ t.strictSame(
108
+ [...res.partial.nodes].map(n => n.name),
109
+ ['a', 'e'],
110
+ 'should select only packages with the specified malware alert',
111
+ )
112
+ t.matchSnapshot({
113
+ nodes: [...res.partial.nodes].map(n => n.name),
114
+ edges: [...res.partial.edges].map(e => e.name),
115
+ })
116
+ })
117
+
118
+ await t.test('filter out using unquoted param', async t => {
119
+ const res = await malware(getState(':malware(high)'))
120
+ t.strictSame(
121
+ [...res.partial.nodes].map(n => n.name),
122
+ ['b', 'f'],
123
+ 'should select only packages with the specified malware alert',
124
+ )
125
+ t.matchSnapshot({
126
+ nodes: [...res.partial.nodes].map(n => n.name),
127
+ edges: [...res.partial.edges].map(e => e.name),
128
+ })
129
+ })
130
+
131
+ await t.test(
132
+ 'greater than comparator with number (unquoted)',
133
+ async t => {
134
+ const res = await malware(getState(':malware(>1)'))
135
+ t.strictSame(
136
+ [...res.partial.nodes].map(n => n.name).sort(),
137
+ ['c', 'd'],
138
+ 'should select packages with malware kind greater than 1',
139
+ )
140
+ t.matchSnapshot({
141
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
142
+ edges: [...res.partial.edges].map(e => e.name).sort(),
143
+ })
144
+ },
145
+ )
146
+
147
+ await t.test(
148
+ 'greater than comparator with string kind (quoted)',
149
+ async t => {
150
+ const res = await malware(getState(':malware(">high")'))
151
+ t.strictSame(
152
+ [...res.partial.nodes].map(n => n.name).sort(),
153
+ ['c', 'd'],
154
+ 'should select packages with malware kind greater than high',
155
+ )
156
+ t.matchSnapshot({
157
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
158
+ edges: [...res.partial.edges].map(e => e.name).sort(),
159
+ })
160
+ },
161
+ )
162
+
163
+ await t.test(
164
+ 'less than comparator with number (unquoted)',
165
+ async t => {
166
+ const res = await malware(getState(':malware(<2)'))
167
+ t.strictSame(
168
+ [...res.partial.nodes].map(n => n.name).sort(),
169
+ ['a', 'b', 'e', 'f'],
170
+ 'should select packages with malware kind less than 2',
171
+ )
172
+ t.matchSnapshot({
173
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
174
+ edges: [...res.partial.edges].map(e => e.name).sort(),
175
+ })
176
+ },
177
+ )
178
+
179
+ await t.test(
180
+ 'less than comparator with string kind (quoted)',
181
+ async t => {
182
+ const res = await malware(getState(':malware("<medium")'))
183
+ t.strictSame(
184
+ [...res.partial.nodes].map(n => n.name).sort(),
185
+ ['a', 'b', 'e', 'f'],
186
+ 'should select packages with malware kind less than medium',
187
+ )
188
+ t.matchSnapshot({
189
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
190
+ edges: [...res.partial.edges].map(e => e.name).sort(),
191
+ })
192
+ },
193
+ )
194
+
195
+ await t.test(
196
+ 'greater than or equal to comparator with number (unquoted)',
197
+ async t => {
198
+ const res = await malware(getState(':malware(>=2)'))
199
+ t.strictSame(
200
+ [...res.partial.nodes].map(n => n.name).sort(),
201
+ ['c', 'd'],
202
+ 'should select packages with malware kind greater than or equal to 2',
203
+ )
204
+ t.matchSnapshot({
205
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
206
+ edges: [...res.partial.edges].map(e => e.name).sort(),
207
+ })
208
+ },
209
+ )
210
+
211
+ await t.test(
212
+ 'greater than or equal to comparator with string kind (quoted)',
213
+ async t => {
214
+ const res = await malware(getState(':malware(">=medium")'))
215
+ t.strictSame(
216
+ [...res.partial.nodes].map(n => n.name).sort(),
217
+ ['c', 'd'],
218
+ 'should select packages with malware kind greater than or equal to medium',
219
+ )
220
+ t.matchSnapshot({
221
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
222
+ edges: [...res.partial.edges].map(e => e.name).sort(),
223
+ })
224
+ },
225
+ )
226
+
227
+ await t.test(
228
+ 'less than or equal to comparator with number (unquoted)',
229
+ async t => {
230
+ const res = await malware(getState(':malware(<=1)'))
231
+ t.strictSame(
232
+ [...res.partial.nodes].map(n => n.name).sort(),
233
+ ['a', 'b', 'e', 'f'],
234
+ 'should select packages with malware kind less than or equal to 1',
235
+ )
236
+ t.matchSnapshot({
237
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
238
+ edges: [...res.partial.edges].map(e => e.name).sort(),
239
+ })
240
+ },
241
+ )
242
+
243
+ await t.test(
244
+ 'less than or equal to comparator with string kind (quoted)',
245
+ async t => {
246
+ const res = await malware(getState(':malware("<=high")'))
247
+ t.strictSame(
248
+ [...res.partial.nodes].map(n => n.name).sort(),
249
+ ['a', 'b', 'e', 'f'],
250
+ 'should select packages with malware kind less than or equal to high',
251
+ )
252
+ t.matchSnapshot({
253
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
254
+ edges: [...res.partial.edges].map(e => e.name).sort(),
255
+ })
256
+ },
257
+ )
258
+
259
+ await t.test(
260
+ 'parameterless :malware matches medium+ severity',
261
+ async t => {
262
+ const res = await malware(getState(':malware'))
263
+ t.strictSame(
264
+ [...res.partial.nodes].map(n => n.name).sort(),
265
+ ['a', 'b', 'c', 'e', 'f'],
266
+ 'should select packages with malware severity >= medium (exclude low/gptAnomaly)',
267
+ )
268
+ // Verify that package 'd' with gptAnomaly (low/3) is excluded
269
+ t.notOk(
270
+ [...res.partial.nodes].some(n => n.name === 'd'),
271
+ 'should exclude package d with gptAnomaly (low severity)',
272
+ )
273
+ t.matchSnapshot({
274
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
275
+ edges: [...res.partial.edges].map(e => e.name).sort(),
276
+ })
277
+ },
278
+ )
279
+
280
+ await t.test('wrong parameter', async t => {
281
+ await t.rejects(
282
+ malware(getState(':malware("")')),
283
+ { message: /Failed to parse :malware selector/ },
284
+ 'should throw an error',
285
+ )
286
+ })
287
+
288
+ await t.test('invalid comparison value', async t => {
289
+ await t.rejects(
290
+ malware(getState(':malware(>invalid)')),
291
+ { message: /Failed to parse :malware selector/ },
292
+ 'should throw an error for invalid comparison value',
293
+ )
294
+ })
295
+
296
+ await t.test('out of range number', async t => {
297
+ await t.rejects(
298
+ malware(getState(':malware(>5)')),
299
+ { message: /Failed to parse :malware selector/ },
300
+ 'should throw an error for out of range number',
301
+ )
302
+ })
303
+ })
304
+
305
+ t.test('missing security archive', async t => {
306
+ const getState = (query: string) => {
307
+ const ast = parse(query)
308
+ const current = ast.first.first
309
+ const state: ParserState = {
310
+ comment: '',
311
+ current,
312
+ initial: {
313
+ edges: new Set(),
314
+ nodes: new Set(),
315
+ },
316
+ partial: {
317
+ edges: new Set(),
318
+ nodes: new Set(),
319
+ },
320
+ collect: {
321
+ edges: new Set(),
322
+ nodes: new Set(),
323
+ },
324
+ cancellable: async () => {},
325
+ walk: async i => i,
326
+ securityArchive: undefined,
327
+ importers: new Set(),
328
+ retries: 0,
329
+ signal: new AbortController().signal,
330
+ specificity: { idCounter: 0, commonCounter: 0 },
331
+ }
332
+ return state
333
+ }
334
+
335
+ await t.rejects(
336
+ malware(getState(':malware(critical)')),
337
+ { message: /Missing security archive/ },
338
+ 'should throw an error',
339
+ )
340
+ })
341
+
342
+ t.test('isMalwareKind', async t => {
343
+ t.ok(
344
+ isMalwareKind('critical'),
345
+ 'should return true for valid malware kinds',
346
+ )
347
+ t.notOk(
348
+ isMalwareKind('invalid'),
349
+ 'should return false for invalid malware kinds',
350
+ )
351
+ })
352
+
353
+ t.test('asMalwareKind', async t => {
354
+ t.equal(
355
+ asMalwareKind('critical'),
356
+ 'critical',
357
+ 'should return the malware kind',
358
+ )
359
+ t.throws(
360
+ () => asMalwareKind('invalid'),
361
+ { message: /Expected a valid malware kind/ },
362
+ 'should throw an error for invalid malware kinds',
363
+ )
364
+ })
365
+
366
+ t.test('parseInternals', async t => {
367
+ const testParseInternals = (query: string) => {
368
+ const ast = parse(query)
369
+ const nodes = asPostcssNodeWithChildren(ast.first.first).nodes
370
+ return parseInternals(nodes)
371
+ }
372
+
373
+ t.strictSame(
374
+ parseInternals([]),
375
+ { kind: undefined, comparator: undefined },
376
+ 'should handle empty nodes array (parameterless :malware)',
377
+ )
378
+
379
+ // Test case for lines 87-88: selector node exists but has no child nodes
380
+ t.strictSame(
381
+ parseInternals([{ type: 'selector', nodes: [] } as any]),
382
+ { kind: undefined, comparator: undefined },
383
+ 'should handle selector node with empty nodes array',
384
+ )
385
+
386
+ t.strictSame(
387
+ testParseInternals(':malware(critical)'),
388
+ { kind: 'critical', comparator: undefined },
389
+ 'should parse simple kind without comparator',
390
+ )
391
+
392
+ t.strictSame(
393
+ testParseInternals(':malware(">1")'),
394
+ { kind: '1', comparator: '>' },
395
+ 'should parse kind with greater than comparator',
396
+ )
397
+
398
+ t.strictSame(
399
+ testParseInternals(':malware(<low)'),
400
+ { kind: 'low', comparator: '<' },
401
+ 'should parse kind with less than comparator',
402
+ )
403
+
404
+ t.strictSame(
405
+ testParseInternals(':malware(">=medium")'),
406
+ { kind: 'medium', comparator: '>=' },
407
+ 'should parse kind with greater than or equal comparator',
408
+ )
409
+
410
+ t.strictSame(
411
+ testParseInternals(':malware(<=2)'),
412
+ { kind: '2', comparator: '<=' },
413
+ 'should parse kind with less than or equal comparator',
414
+ )
415
+
416
+ t.throws(
417
+ () => testParseInternals(':malware(>invalid)'),
418
+ {
419
+ message: /Expected a valid malware kind or number between 0-3/,
420
+ },
421
+ 'should throw for invalid kind with comparator',
422
+ )
423
+
424
+ t.throws(
425
+ () => testParseInternals(':malware(>4)'),
426
+ {
427
+ message: /Expected a valid malware kind or number between 0-3/,
428
+ },
429
+ 'should throw for out of range number with comparator',
430
+ )
431
+ })
@@ -0,0 +1,98 @@
1
+ import t from 'tap'
2
+ import { joinDepIDTuple } from '@vltpkg/dep-id'
3
+ import { asSecurityArchiveLike } from '@vltpkg/security-archive'
4
+ import { getSimpleGraph } from '../fixtures/graph.ts'
5
+ import type { ParserState } from '../../src/types.ts'
6
+ import { minified } from '../../src/pseudo/minified.ts'
7
+ import { parse } from '../../src/parser.ts'
8
+
9
+ t.test('selects packages with a minifiedFile alert', async t => {
10
+ const getState = (query: string, graph = getSimpleGraph()) => {
11
+ const ast = parse(query)
12
+ const current = ast.first.first
13
+ const state: ParserState = {
14
+ comment: '',
15
+ current,
16
+ initial: {
17
+ edges: new Set(graph.edges.values()),
18
+ nodes: new Set(graph.nodes.values()),
19
+ },
20
+ partial: {
21
+ edges: new Set(graph.edges.values()),
22
+ nodes: new Set(graph.nodes.values()),
23
+ },
24
+ collect: {
25
+ edges: new Set(),
26
+ nodes: new Set(),
27
+ },
28
+ cancellable: async () => {},
29
+ walk: async i => i,
30
+ securityArchive: asSecurityArchiveLike(
31
+ new Map([
32
+ [
33
+ joinDepIDTuple(['registry', '', 'e@1.0.0']),
34
+ { alerts: [{ type: 'minifiedFile' }] },
35
+ ],
36
+ ]),
37
+ ),
38
+ importers: new Set(graph.importers),
39
+ retries: 0,
40
+ signal: new AbortController().signal,
41
+ specificity: { idCounter: 0, commonCounter: 0 },
42
+ }
43
+ return state
44
+ }
45
+
46
+ await t.test(
47
+ 'filter out any node that does not have the alert',
48
+ async t => {
49
+ const res = await minified(getState(':minified'))
50
+ t.strictSame(
51
+ [...res.partial.nodes].map(n => n.name),
52
+ ['e'],
53
+ 'should select only minified packages',
54
+ )
55
+ t.matchSnapshot({
56
+ nodes: [...res.partial.nodes].map(n => n.name),
57
+ edges: [...res.partial.edges].map(e => e.name),
58
+ })
59
+ },
60
+ )
61
+ })
62
+
63
+ t.test('missing security archive', async t => {
64
+ const getState = (query: string) => {
65
+ const ast = parse(query)
66
+ const current = ast.first.first
67
+ const state: ParserState = {
68
+ comment: '',
69
+ current,
70
+ initial: {
71
+ edges: new Set(),
72
+ nodes: new Set(),
73
+ },
74
+ partial: {
75
+ edges: new Set(),
76
+ nodes: new Set(),
77
+ },
78
+ collect: {
79
+ edges: new Set(),
80
+ nodes: new Set(),
81
+ },
82
+ cancellable: async () => {},
83
+ walk: async i => i,
84
+ securityArchive: undefined,
85
+ importers: new Set(),
86
+ retries: 0,
87
+ signal: new AbortController().signal,
88
+ specificity: { idCounter: 0, commonCounter: 0 },
89
+ }
90
+ return state
91
+ }
92
+
93
+ await t.rejects(
94
+ minified(getState(':minified')),
95
+ { message: /Missing security archive/ },
96
+ 'should throw an error',
97
+ )
98
+ })
@@ -0,0 +1,111 @@
1
+ import t from 'tap'
2
+ import postcssSelectorParser from 'postcss-selector-parser'
3
+ import type { ParserState } from '../../src/types.ts'
4
+ import { missing } from '../../src/pseudo/missing.ts'
5
+ import {
6
+ getSimpleGraph,
7
+ getMissingNodeGraph,
8
+ } from '../fixtures/graph.ts'
9
+
10
+ t.test('selects edges with no linked node', async t => {
11
+ const getState = (query: string, graph = getSimpleGraph()) => {
12
+ const ast = postcssSelectorParser().astSync(query)
13
+ const current = ast.first.first
14
+ const state: ParserState = {
15
+ comment: '',
16
+ current,
17
+ initial: {
18
+ edges: new Set(graph.edges.values()),
19
+ nodes: new Set(graph.nodes.values()),
20
+ },
21
+ partial: {
22
+ edges: new Set(graph.edges.values()),
23
+ nodes: new Set(graph.nodes.values()),
24
+ },
25
+ collect: {
26
+ edges: new Set(),
27
+ nodes: new Set(),
28
+ },
29
+ cancellable: async () => {},
30
+ walk: async i => i,
31
+ retries: 0,
32
+ securityArchive: undefined,
33
+ importers: new Set(graph.importers),
34
+ signal: new AbortController().signal,
35
+ specificity: { idCounter: 0, commonCounter: 0 },
36
+ }
37
+ return state
38
+ }
39
+
40
+ await t.test(
41
+ 'returns no edges in simple graph (all edges have linked nodes)',
42
+ async t => {
43
+ // In getSimpleGraph, all edges have linked nodes, so :missing should return no edges
44
+ const res = await missing(getState(':missing'))
45
+ t.strictSame(
46
+ [...res.partial.edges].map(e => e.name),
47
+ [],
48
+ 'should not select any edges in a graph where all edges have linked nodes',
49
+ )
50
+ t.strictSame(
51
+ [...res.partial.nodes].map(n => n.name),
52
+ [],
53
+ 'should always return an empty nodes set',
54
+ )
55
+ t.matchSnapshot({
56
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
57
+ edges: [...res.partial.edges].map(e => e.name).sort(),
58
+ })
59
+ },
60
+ )
61
+
62
+ await t.test(
63
+ 'selects edges with no linked node in missing node graph',
64
+ async t => {
65
+ // In getMissingNodeGraph, there are edges without linked nodes
66
+ const missingNodeGraph = getMissingNodeGraph()
67
+ const res = await missing(
68
+ getState(':missing', missingNodeGraph),
69
+ )
70
+
71
+ // The getMissingNodeGraph has edges 'a' and 'b' without linked nodes
72
+ t.strictSame(
73
+ [...res.partial.edges].map(e => e.name).sort(),
74
+ ['a', 'b'].sort(),
75
+ 'should select only edges without linked nodes',
76
+ )
77
+ t.strictSame(
78
+ [...res.partial.nodes],
79
+ [],
80
+ 'should always return an empty nodes set',
81
+ )
82
+ t.matchSnapshot({
83
+ nodes: [...res.partial.nodes].map(n => n.name).sort(),
84
+ edges: [...res.partial.edges].map(e => e.name).sort(),
85
+ })
86
+ },
87
+ )
88
+
89
+ await t.test('handles an empty partial state', async t => {
90
+ // Create a state with empty partial edges and nodes
91
+ const state = getState(':missing')
92
+ state.partial.nodes.clear()
93
+ state.partial.edges.clear()
94
+
95
+ const res = await missing(state)
96
+ t.strictSame(
97
+ [...res.partial.edges],
98
+ [],
99
+ 'should return empty array of edges when starting with empty partial state',
100
+ )
101
+ t.strictSame(
102
+ [...res.partial.nodes],
103
+ [],
104
+ 'should return empty array of nodes when starting with empty partial state',
105
+ )
106
+ t.matchSnapshot({
107
+ nodes: [...res.partial.nodes].map(n => n.name),
108
+ edges: [...res.partial.edges].map(e => e.name),
109
+ })
110
+ })
111
+ })