npq 3.8.1 → 3.9.1

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 (48) hide show
  1. package/.github/npq.mov +0 -0
  2. package/.github/npq.mp4 +0 -0
  3. package/.github/npq.png +0 -0
  4. package/.github/workflows/main.yml +2 -2
  5. package/README.md +18 -9
  6. package/__tests__/__fixtures__/test.marshall.js +5 -6
  7. package/__tests__/cli.test.js +110 -0
  8. package/__tests__/cliPrompt.test.js +409 -0
  9. package/__tests__/marshalls.base.test.js +1 -1
  10. package/__tests__/marshalls.classMethods.test.js +11 -14
  11. package/__tests__/marshalls.expiredDomains.test.js +2 -2
  12. package/__tests__/marshalls.provenance.test.js +181 -4
  13. package/__tests__/marshalls.signatures.test.js +0 -4
  14. package/__tests__/marshalls.snyk.test.js +294 -0
  15. package/__tests__/marshalls.tasks.test.js +48 -24
  16. package/__tests__/marshalls.typosquatting.test.js +16 -9
  17. package/__tests__/reportResults.test.js +772 -0
  18. package/__tests__/scripts.test.js +2 -2
  19. package/bin/npq-hero.js +73 -20
  20. package/bin/npq.js +113 -21
  21. package/lib/cli.js +140 -24
  22. package/lib/helpers/cliPrompt.js +97 -0
  23. package/lib/helpers/cliSpinner.js +104 -0
  24. package/lib/helpers/cliSupportHandler.js +50 -7
  25. package/lib/helpers/packageRepoUtils.js +5 -0
  26. package/lib/helpers/promiseThrottler.js +96 -0
  27. package/lib/helpers/reportResults.js +304 -0
  28. package/lib/helpers/sourcePackages.js +36 -0
  29. package/lib/marshall.js +42 -65
  30. package/lib/marshalls/age.marshall.js +3 -2
  31. package/lib/marshalls/author.marshall.js +36 -7
  32. package/lib/marshalls/baseMarshall.js +9 -17
  33. package/lib/marshalls/deprecation.marshall.js +43 -0
  34. package/lib/marshalls/downloads.marshall.js +1 -1
  35. package/lib/marshalls/expiredDomains.marshall.js +7 -5
  36. package/lib/marshalls/index.js +110 -80
  37. package/lib/marshalls/license.marshall.js +1 -1
  38. package/lib/marshalls/newbin.marshall.js +1 -2
  39. package/lib/marshalls/provenance.marshall.js +152 -33
  40. package/lib/marshalls/repo.marshall.js +6 -5
  41. package/lib/marshalls/scripts.marshall.js +1 -1
  42. package/lib/marshalls/signatures.marshall.js +26 -2
  43. package/lib/marshalls/snyk.marshall.js +61 -15
  44. package/lib/marshalls/typosquatting.marshall.js +8 -5
  45. package/lib/marshalls/version-maturity.marshall.js +1 -1
  46. package/package.json +16 -19
  47. package/scripts/postinstall.js +10 -11
  48. package/lib/cliCommons.js +0 -80
@@ -2,29 +2,26 @@ beforeEach(() => {
2
2
  jest.resetModules()
3
3
  })
4
4
 
5
- jest.mock('glob', () => {
6
- return {
7
- glob: jest.fn().mockResolvedValue(['file1', 'file2'])
8
- }
9
- })
10
-
11
5
  test('collecting marshalls resolves with files array', async () => {
12
6
  const marshalls = require('../lib/marshalls')
13
7
 
14
8
  const marshallFiles = await marshalls.collectMarshalls()
15
- expect(marshallFiles).toEqual(['file1', 'file2'])
9
+ expect(marshallFiles.length).toBeGreaterThan(0)
10
+ expect(marshallFiles.every((file) => file.endsWith('.marshall.js'))).toBe(true)
16
11
  })
17
12
 
18
- test('collecting marshalls resolves with error if unable to process files', () => {
13
+ test('collecting marshalls handles directory read errors', async () => {
14
+ const fs = require('node:fs')
19
15
  const marshalls = require('../lib/marshalls')
20
16
 
21
- jest.mock('glob', () => {
22
- return {
23
- glob: jest.fn().mockRejectedValue(new Error('error'))
24
- }
25
- })
17
+ // Mock fs.promises.readdir to throw an error
18
+ const originalReaddir = fs.promises.readdir
19
+ fs.promises.readdir = jest.fn().mockRejectedValue(new Error('Directory read error'))
20
+
21
+ await expect(marshalls.collectMarshalls()).rejects.toThrow('Directory read error')
26
22
 
27
- expect(marshalls.collectMarshalls()).rejects.toEqual(expect.any(Error))
23
+ // Restore original function
24
+ fs.promises.readdir = originalReaddir
28
25
  })
29
26
 
30
27
  test('build marshalls without any should throw error', () => {
@@ -43,7 +43,7 @@ describe('Expired domains test suites', () => {
43
43
  }
44
44
 
45
45
  await expect(testMarshall.validate(pkgData)).rejects.toThrow(
46
- /Unable to resolve domain for maintainer e-mail, could be an expired account/
46
+ /Detected expired domain can be abused for account takeover/
47
47
  )
48
48
  })
49
49
 
@@ -69,7 +69,7 @@ describe('Expired domains test suites', () => {
69
69
  }
70
70
 
71
71
  await expect(testMarshall.validate(pkgData)).rejects.toThrow(
72
- /Unable to resolve domain for maintainer e-mail, could be an expired account: <unknown>/
72
+ /Detected expired domain can be abused for account takeover: <unknown>/
73
73
  )
74
74
  })
75
75
 
@@ -140,9 +140,10 @@ describe('Provenance test suites', () => {
140
140
  }
141
141
  })
142
142
 
143
- // We assert that the validate method didn't throw an error,
144
- // because the keys match the signature
145
- await expect(testMarshall.validate(pkg)).rejects.toThrow('mocked manifest error')
143
+ // We assert that the validate method throws an error,
144
+ // but we can't assert the exact error message because we don't log
145
+ // it unless debugging is enabled
146
+ await expect(testMarshall.validate(pkg)).rejects.toThrow()
146
147
 
147
148
  // Assert that the fetch method is called with the correct URL
148
149
  // eslint-disable-next-line no-undef
@@ -196,11 +197,187 @@ describe('Provenance test suites', () => {
196
197
  // We assert that the validate method didn't throw an error,
197
198
  // because the keys match the signature
198
199
  await expect(testMarshall.validate(pkg)).rejects.toThrow(
199
- 'Unable to verify provenance: the package was published without any attestations.'
200
+ 'Unable to verify provenance: the package was published without any attestations'
200
201
  )
201
202
 
202
203
  // Assert that the fetch method is called with the correct URL
203
204
  // eslint-disable-next-line no-undef
204
205
  expect(fetch).toHaveBeenCalledWith('https://registry.npmjs.org/-/npm/v1/keys')
205
206
  })
207
+
208
+ test.skip('should throw error when provenance regression is detected', async () => {
209
+ // Mock fetch to return registry keys
210
+ const mockResponse = {
211
+ json: jest.fn().mockResolvedValue({
212
+ keys: [{ key: 'publicKey1' }]
213
+ })
214
+ }
215
+ global.fetch = jest.fn().mockResolvedValue(mockResponse)
216
+
217
+ const pkg = {
218
+ packageName: 'test-package',
219
+ packageVersion: '2.0.0'
220
+ }
221
+
222
+ // Mock pacote.manifest to simulate:
223
+ // - version 1.0.0 has attestations (older version with provenance)
224
+ // - version 2.0.0 has no attestations (newer version without provenance)
225
+ pacote.manifest = jest
226
+ .fn()
227
+ .mockImplementationOnce((manifest) => {
228
+ if (manifest.includes('1.0.0')) {
229
+ return Promise.resolve({
230
+ name: 'test-package',
231
+ version: '1.0.0',
232
+ _attestations: { provenance: 'test' }
233
+ })
234
+ }
235
+ return Promise.reject(new Error('No attestations'))
236
+ })
237
+ .mockImplementationOnce((manifest) => {
238
+ if (manifest.includes('2.0.0')) {
239
+ return Promise.resolve({
240
+ name: 'test-package',
241
+ version: '2.0.0'
242
+ // No _attestations property
243
+ })
244
+ }
245
+ return Promise.reject(new Error('No attestations'))
246
+ })
247
+
248
+ const testMarshall = new ProvenanceMarshall({
249
+ packageRepoUtils: {
250
+ getPackageInfo: () => {
251
+ return Promise.resolve({
252
+ name: 'test-package',
253
+ versions: {
254
+ '1.0.0': { name: 'test-package', version: '1.0.0' },
255
+ '2.0.0': { name: 'test-package', version: '2.0.0' }
256
+ }
257
+ })
258
+ },
259
+ parsePackageVersion: (version) => ({ version })
260
+ }
261
+ })
262
+
263
+ await expect(testMarshall.validate(pkg)).rejects.toThrow(
264
+ 'Provenance regression detected: Previous version 1.0.0 had provenance attestations, but version 2.0.0 does not. This represents a security downgrade.'
265
+ )
266
+ })
267
+
268
+ test('should pass when no older versions exist', async () => {
269
+ const mockResponse = {
270
+ json: jest.fn().mockResolvedValue({
271
+ keys: [{ key: 'publicKey1' }]
272
+ })
273
+ }
274
+ global.fetch = jest.fn().mockResolvedValue(mockResponse)
275
+
276
+ const pkg = {
277
+ packageName: 'new-package',
278
+ packageVersion: '1.0.0'
279
+ }
280
+
281
+ pacote.manifest = jest.fn().mockResolvedValue({
282
+ name: 'new-package',
283
+ version: '1.0.0',
284
+ _attestations: { provenance: 'test' }
285
+ })
286
+
287
+ const testMarshall = new ProvenanceMarshall({
288
+ packageRepoUtils: {
289
+ getPackageInfo: () => {
290
+ return Promise.resolve({
291
+ name: 'new-package',
292
+ versions: {
293
+ '1.0.0': { name: 'new-package', version: '1.0.0' }
294
+ }
295
+ })
296
+ },
297
+ parsePackageVersion: (version) => ({ version })
298
+ }
299
+ })
300
+
301
+ const result = await testMarshall.validate(pkg)
302
+ expect(result).toBeDefined()
303
+ })
304
+
305
+ test('should pass when no older versions had provenance', async () => {
306
+ const mockResponse = {
307
+ json: jest.fn().mockResolvedValue({
308
+ keys: [{ key: 'publicKey1' }]
309
+ })
310
+ }
311
+ global.fetch = jest.fn().mockResolvedValue(mockResponse)
312
+
313
+ const pkg = {
314
+ packageName: 'test-package',
315
+ packageVersion: '2.0.0'
316
+ }
317
+
318
+ // Both versions lack attestations
319
+ pacote.manifest = jest.fn().mockResolvedValue({
320
+ name: 'test-package',
321
+ version: '2.0.0'
322
+ // No _attestations
323
+ })
324
+
325
+ const testMarshall = new ProvenanceMarshall({
326
+ packageRepoUtils: {
327
+ getPackageInfo: () => {
328
+ return Promise.resolve({
329
+ name: 'test-package',
330
+ versions: {
331
+ '1.0.0': { name: 'test-package', version: '1.0.0' },
332
+ '2.0.0': { name: 'test-package', version: '2.0.0' }
333
+ }
334
+ })
335
+ },
336
+ parsePackageVersion: (version) => ({ version })
337
+ }
338
+ })
339
+
340
+ await expect(testMarshall.validate(pkg)).rejects.toThrow(
341
+ 'the package was published without any attestations'
342
+ )
343
+ })
344
+
345
+ test('should pass when current version maintains provenance', async () => {
346
+ const mockResponse = {
347
+ json: jest.fn().mockResolvedValue({
348
+ keys: [{ key: 'publicKey1' }]
349
+ })
350
+ }
351
+ global.fetch = jest.fn().mockResolvedValue(mockResponse)
352
+
353
+ const pkg = {
354
+ packageName: 'test-package',
355
+ packageVersion: '2.0.0'
356
+ }
357
+
358
+ // Both versions have attestations
359
+ pacote.manifest = jest.fn().mockResolvedValue({
360
+ name: 'test-package',
361
+ version: '2.0.0',
362
+ _attestations: { provenance: 'test' }
363
+ })
364
+
365
+ const testMarshall = new ProvenanceMarshall({
366
+ packageRepoUtils: {
367
+ getPackageInfo: () => {
368
+ return Promise.resolve({
369
+ name: 'test-package',
370
+ versions: {
371
+ '1.0.0': { name: 'test-package', version: '1.0.0' },
372
+ '2.0.0': { name: 'test-package', version: '2.0.0' }
373
+ }
374
+ })
375
+ },
376
+ parsePackageVersion: (version) => ({ version })
377
+ }
378
+ })
379
+
380
+ const result = await testMarshall.validate(pkg)
381
+ expect(result).toBeDefined()
382
+ })
206
383
  })
@@ -128,9 +128,5 @@ describe('Signature test suites', () => {
128
128
  // We assert that the validate method didn't throw an error,
129
129
  // because the keys match the signature
130
130
  await expect(testMarshall.validate(pkg)).rejects.toThrow('mocked manifest error')
131
-
132
- // Assert that the fetch method is called with the correct URL
133
- // eslint-disable-next-line no-undef
134
- expect(fetch).toHaveBeenCalledWith('https://registry.npmjs.org/-/npm/v1/keys')
135
131
  })
136
132
  })
@@ -0,0 +1,294 @@
1
+ 'use strict'
2
+
3
+ const Marshall = require('../lib/marshalls/snyk.marshall.js')
4
+ const { marshallCategories } = require('../lib/marshalls/constants')
5
+
6
+ const fs = require('fs')
7
+ const os = require('os')
8
+
9
+ jest.mock('fs')
10
+ jest.mock('os')
11
+
12
+ os.homedir.mockReturnValue('/fake/home')
13
+ fs.statSync.mockImplementation(() => {
14
+ throw new Error('File not found')
15
+ })
16
+
17
+ // Mock fetch globally
18
+ global.fetch = jest.fn()
19
+
20
+ const mockPackageRepoUtils = {
21
+ getLatestVersion: jest.fn().mockResolvedValue('1.0.0')
22
+ }
23
+
24
+ describe('Snyk Marshall', () => {
25
+ beforeEach(() => {
26
+ os.homedir.mockReturnValue('/fake/home')
27
+ fetch.mockClear()
28
+ mockPackageRepoUtils.getLatestVersion.mockClear()
29
+ })
30
+
31
+ describe('with Snyk token', () => {
32
+ const marshall = new Marshall({
33
+ packageRepoUtils: mockPackageRepoUtils
34
+ })
35
+ // Manually set the token for this test suite
36
+ marshall.snykApiToken = 'fake-snyk-token'
37
+
38
+ it('has the right title', () => {
39
+ expect(marshall.title()).toBe('Checking for known vulnerabilities')
40
+ })
41
+
42
+ it('should throw an error if vulnerabilities are found', async () => {
43
+ fetch.mockResolvedValueOnce({
44
+ ok: true,
45
+ json: () =>
46
+ Promise.resolve({
47
+ vulnerabilities: [{ title: 'XSS' }, { title: 'CSRF' }],
48
+ isMaliciousPackage: false
49
+ })
50
+ })
51
+
52
+ const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
53
+ await expect(marshall.validate(pkg)).rejects.toThrow(
54
+ '2 vulnerable path(s) found: https://snyk.io/vuln/npm:test-pkg'
55
+ )
56
+ })
57
+
58
+ it('should throw a specific error for malicious packages', async () => {
59
+ fetch.mockResolvedValueOnce({
60
+ ok: true,
61
+ json: () =>
62
+ Promise.resolve({
63
+ vulnerabilities: [{ title: 'Malicious Package' }],
64
+ isMaliciousPackage: true
65
+ })
66
+ })
67
+
68
+ const pkg = { packageName: 'malicious-pkg', packageVersion: '1.0.0' }
69
+ await expect(marshall.validate(pkg)).rejects.toThrow(
70
+ 'Malicious package found: https://snyk.io/vuln/npm:malicious-pkg'
71
+ )
72
+ })
73
+
74
+ it('should pass if no vulnerabilities are found', async () => {
75
+ fetch.mockResolvedValueOnce({
76
+ ok: true,
77
+ json: () =>
78
+ Promise.resolve({
79
+ vulnerabilities: [],
80
+ isMaliciousPackage: false
81
+ })
82
+ })
83
+
84
+ const pkg = { packageName: 'clean-pkg', packageVersion: '1.0.0' }
85
+ await expect(marshall.validate(pkg)).resolves.not.toThrow()
86
+ })
87
+
88
+ it('should throw an error if the Snyk API request fails', async () => {
89
+ fetch.mockResolvedValueOnce({
90
+ ok: false,
91
+ status: 500
92
+ })
93
+
94
+ const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
95
+ await expect(marshall.validate(pkg)).rejects.toThrow(
96
+ 'Snyk API request failed with status 500'
97
+ )
98
+ })
99
+
100
+ it('should throw an error if Snyk API provides no vulnerability info', async () => {
101
+ fetch.mockResolvedValueOnce({
102
+ ok: true,
103
+ json: () => Promise.resolve({ message: 'no vulns here' }) // No 'vulnerabilities' property
104
+ })
105
+
106
+ const pkg = { packageName: 'test-pkg', packageVersion: '1.0.0' }
107
+ // This will cause getSnykVulnInfo to return `false`, which makes validate throw
108
+ await expect(marshall.validate(pkg)).rejects.toThrow(
109
+ 'Unable to query vulnerabilities for packages'
110
+ )
111
+ })
112
+
113
+ it('should use latest version if packageVersion is not defined', async () => {
114
+ fetch.mockResolvedValueOnce({
115
+ ok: true,
116
+ json: () =>
117
+ Promise.resolve({
118
+ vulnerabilities: [],
119
+ isMaliciousPackage: false
120
+ })
121
+ })
122
+
123
+ const pkg = { packageName: 'test-pkg' }
124
+ await marshall.validate(pkg)
125
+
126
+ expect(mockPackageRepoUtils.getLatestVersion).toHaveBeenCalledWith('test-pkg')
127
+ })
128
+ })
129
+
130
+ describe('without Snyk token (fallback to OSV)', () => {
131
+ const marshall = new Marshall({
132
+ packageRepoUtils: mockPackageRepoUtils
133
+ })
134
+ // Ensure no token is set
135
+ marshall.snykApiToken = null
136
+
137
+ it('should throw an error if vulnerabilities are found by OSV', async () => {
138
+ fetch.mockResolvedValueOnce({
139
+ ok: true,
140
+ json: () => Promise.resolve({ vulns: [{ id: 'OSV-1' }, { id: 'OSV-2' }] })
141
+ })
142
+
143
+ const pkg = { packageName: 'test-pkg-osv', packageVersion: '1.0.0' }
144
+ await expect(marshall.validate(pkg)).rejects.toThrow(
145
+ '2 vulnerabilities found by OSV for test-pkg-osv'
146
+ )
147
+ expect(fetch).toHaveBeenCalledWith('https://api.osv.dev/v1/query', expect.any(Object))
148
+ })
149
+
150
+ it('should pass if no vulnerabilities are found by OSV', async () => {
151
+ fetch.mockResolvedValueOnce({
152
+ ok: true,
153
+ json: () => Promise.resolve({ vulns: [] })
154
+ })
155
+
156
+ const pkg = { packageName: 'clean-pkg-osv', packageVersion: '1.0.0' }
157
+ await expect(marshall.validate(pkg)).resolves.not.toThrow()
158
+ })
159
+
160
+ it('should handle an empty response from OSV', async () => {
161
+ fetch.mockResolvedValueOnce({
162
+ ok: true,
163
+ json: () => Promise.resolve({})
164
+ })
165
+
166
+ const pkg = { packageName: 'empty-res-pkg', packageVersion: '1.0.0' }
167
+ await expect(marshall.validate(pkg)).resolves.toEqual({
168
+ issuesCount: 0,
169
+ isMaliciousPackage: false
170
+ })
171
+ })
172
+
173
+ it('should handle OSV API fetch error', async () => {
174
+ fetch.mockRejectedValueOnce(new Error('Network error'))
175
+
176
+ const pkg = { packageName: 'fetch-err-pkg', packageVersion: '1.0.0' }
177
+ const result = await marshall.validate(pkg)
178
+
179
+ expect(result).toEqual({ issuesCount: 0, isMaliciousPackage: false })
180
+ })
181
+ })
182
+
183
+ describe('run method', () => {
184
+ const marshall = new Marshall({
185
+ packageRepoUtils: mockPackageRepoUtils
186
+ })
187
+
188
+ it('should call checkPackage for each package', async () => {
189
+ const ctx = {
190
+ pkgs: [
191
+ { packageName: 'pkg1', packageVersion: '1.0.0' },
192
+ { packageName: 'pkg2', packageVersion: '2.0.0' }
193
+ ]
194
+ }
195
+ const task = {}
196
+ marshall.checkPackage = jest.fn()
197
+
198
+ await marshall.run(ctx, task)
199
+
200
+ expect(marshall.checkPackage).toHaveBeenCalledTimes(2)
201
+ expect(marshall.checkPackage).toHaveBeenCalledWith(ctx.pkgs[0], ctx, task)
202
+ expect(marshall.checkPackage).toHaveBeenCalledWith(ctx.pkgs[1], ctx, task)
203
+ })
204
+ })
205
+
206
+ describe('getSnykToken', () => {
207
+ it('should return null if config file does not exist', () => {
208
+ fs.statSync.mockImplementation(() => {
209
+ throw new Error('ENOENT')
210
+ })
211
+
212
+ const marshall = new Marshall({
213
+ packageRepoUtils: mockPackageRepoUtils
214
+ })
215
+ marshall.snykApiToken = null // ensure no token
216
+ const token = marshall.getSnykToken()
217
+ expect(token).toBeNull()
218
+ })
219
+
220
+ it('should return null if config file exists but has no token', () => {
221
+ fs.statSync.mockReturnValueOnce({ isFile: () => true })
222
+ jest.doMock('/fake/home/.config/configstore/snyk.json', () => ({}), { virtual: true })
223
+
224
+ const marshall = new Marshall({
225
+ packageRepoUtils: mockPackageRepoUtils
226
+ })
227
+ marshall.snykApiToken = null // ensure no token
228
+ const token = marshall.getSnykToken()
229
+ expect(token).toBeNull()
230
+ })
231
+
232
+ it('should return the token if the config file exists and has a token', () => {
233
+ fs.statSync.mockImplementation(() => ({ isFile: () => true }))
234
+ fs.readFileSync.mockReturnValue(JSON.stringify({ api: 'a-real-token' }))
235
+
236
+ const marshall = new Marshall({
237
+ packageRepoUtils: mockPackageRepoUtils
238
+ })
239
+ marshall.snykApiToken = null // ensure no token
240
+ const token = marshall.getSnykToken()
241
+ expect(token).toBe('a-real-token')
242
+
243
+ // Reset mock to avoid affecting other tests
244
+ fs.statSync.mockImplementation(() => {
245
+ throw new Error('File not found')
246
+ })
247
+ })
248
+
249
+ it('should return null if config file is malformed', () => {
250
+ fs.statSync.mockImplementation(() => ({ isFile: () => true }))
251
+ fs.readFileSync.mockReturnValue('not a valid json')
252
+
253
+ const marshall = new Marshall({
254
+ packageRepoUtils: mockPackageRepoUtils
255
+ })
256
+ marshall.snykApiToken = null // ensure no token
257
+ const token = marshall.getSnykToken()
258
+ expect(token).toBeNull()
259
+ })
260
+
261
+ it('should return null if statSync returns falsy', () => {
262
+ fs.statSync.mockReturnValue(false)
263
+
264
+ const marshall = new Marshall({
265
+ packageRepoUtils: mockPackageRepoUtils
266
+ })
267
+ marshall.snykApiToken = null // ensure no token
268
+ const token = marshall.getSnykToken()
269
+ expect(token).toBeNull()
270
+ })
271
+ })
272
+
273
+ describe('getSnykToken with env var', () => {
274
+ const OLD_ENV = process.env
275
+
276
+ beforeEach(() => {
277
+ jest.resetModules() // Most important - it clears the cache
278
+ process.env = { ...OLD_ENV } // Make a copy
279
+ })
280
+
281
+ afterAll(() => {
282
+ process.env = OLD_ENV // Restore old environment
283
+ })
284
+
285
+ it('should return token from SNYK_TOKEN environment variable', () => {
286
+ process.env.SNYK_TOKEN = 'token-from-env'
287
+ const MarshallWithEnv = require('../lib/marshalls/snyk.marshall.js')
288
+ const marshall = new MarshallWithEnv({
289
+ packageRepoUtils: mockPackageRepoUtils
290
+ })
291
+ expect(marshall.getSnykToken()).toBe('token-from-env')
292
+ })
293
+ })
294
+ })
@@ -13,19 +13,19 @@ test('running marshall tasks succeeds', async () => {
13
13
  })
14
14
 
15
15
  const config = {
16
- pkgs: ['express', 'semver'],
16
+ pkgs: [{ packageName: 'express' }, { packageName: 'semver' }],
17
17
  packageRepoUtils: new PackageRepoUtilsMock()
18
18
  }
19
19
 
20
- const tasks = await marshalls.tasks(config)
21
- expect(tasks.pkgs).toEqual(['express', 'semver'])
22
- expect(tasks.marshalls).toEqual({
23
- 'test.marshall': {
24
- status: null,
25
- errors: [],
26
- warnings: [],
27
- data: { express: 'mock data check', semver: 'mock data check' }
28
- }
20
+ const results = await marshalls.tasks(config)
21
+
22
+ expect(results[0]['test.marshall']).toEqual({
23
+ status: null,
24
+ errors: [],
25
+ warnings: [],
26
+ data: { express: 'mock data check', semver: 'mock data check' },
27
+ marshall: 'test.marshall',
28
+ categoryId: 'PackageHealth'
29
29
  })
30
30
  })
31
31
 
@@ -35,13 +35,12 @@ test('running marshall tasks fails', async () => {
35
35
  })
36
36
 
37
37
  const config = {
38
- pkgs: ['express', 'dockly'],
38
+ pkgs: [{ packageName: 'express' }, { packageName: 'dockly' }],
39
39
  packageRepoUtils: new PackageRepoUtilsMock()
40
40
  }
41
41
 
42
- const context = {
43
- pkgs: ['express', 'dockly'],
44
- marshalls: {
42
+ const mockedResults = [
43
+ {
45
44
  'test.marshall': {
46
45
  status: null,
47
46
  errors: [{ pkg: 'dockly', message: 'simulating mock error' }],
@@ -49,12 +48,12 @@ test('running marshall tasks fails', async () => {
49
48
  data: { express: 'mock data check' }
50
49
  }
51
50
  }
52
- }
51
+ ]
53
52
 
54
- await expect(marshalls.tasks(config)).resolves.toMatchObject(context)
53
+ await expect(marshalls.tasks(config)).resolves.toMatchObject(mockedResults)
55
54
  })
56
55
 
57
- test('running marshall tasks throws error when single package is not found', async () => {
56
+ test('running marshall tasks includes an error when single package is not found', async () => {
58
57
  const PackageRepoUtilsNotFoundMock = class Fake {
59
58
  getPackageInfo() {
60
59
  return Promise.resolve({ error: 'Not found' })
@@ -70,10 +69,20 @@ test('running marshall tasks throws error when single package is not found', asy
70
69
  packageRepoUtils: new PackageRepoUtilsNotFoundMock()
71
70
  }
72
71
 
73
- await expect(marshalls.tasks(config)).resolves.toEqual({
74
- error: true,
75
- message: 'Package not found: nonexistent-package'
76
- })
72
+ const mockedResults = [
73
+ {
74
+ not_found: {
75
+ status: null,
76
+ errors: [{ pkg: 'nonexistent-package', message: 'Package not found' }],
77
+ warnings: [],
78
+ data: {},
79
+ marshall: 'not_found',
80
+ categoryId: 'PackageHealth'
81
+ }
82
+ }
83
+ ]
84
+
85
+ await expect(marshalls.tasks(config)).resolves.toMatchObject(mockedResults)
77
86
  })
78
87
 
79
88
  test('running marshall tasks filters out not found packages when multiple packages provided', async () => {
@@ -100,7 +109,22 @@ test('running marshall tasks filters out not found packages when multiple packag
100
109
  }
101
110
 
102
111
  const result = await marshalls.tasks(config)
103
- // Should filter out the nonexistent package and keep only express and semver
104
- expect(result.pkgs).toHaveLength(2)
105
- expect(result.pkgs).toEqual([{ packageName: 'express' }, { packageName: 'semver' }])
112
+
113
+ expect(result[0]['not_found']).toEqual({
114
+ status: null,
115
+ errors: [{ pkg: 'nonexistent-package', message: 'Package not found' }],
116
+ warnings: [],
117
+ data: {},
118
+ marshall: 'not_found',
119
+ categoryId: 'PackageHealth'
120
+ })
121
+
122
+ expect(result[1]['test.marshall']).toEqual({
123
+ status: null,
124
+ errors: [],
125
+ warnings: [],
126
+ data: { express: 'mock data check', semver: 'mock data check' },
127
+ marshall: 'test.marshall',
128
+ categoryId: 'PackageHealth'
129
+ })
106
130
  })