npq 3.10.1 → 3.11.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 (46) hide show
  1. package/package.json +27 -20
  2. package/.editorconfig +0 -49
  3. package/.github/ISSUE_TEMPLATE.md +0 -32
  4. package/.github/Logo Horizontal.png +0 -0
  5. package/.github/Logo Vertical.png +0 -0
  6. package/.github/Logo.png +0 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +0 -35
  8. package/.github/npq-demo-1.gif +0 -0
  9. package/.github/npq-demo-1.png +0 -0
  10. package/.github/npq-demo.gif +0 -0
  11. package/.github/npq.mov +0 -0
  12. package/.github/npq.mp4 +0 -0
  13. package/.github/npq.png +0 -0
  14. package/.github/workflows/automerge.yml +0 -39
  15. package/.github/workflows/main.yml +0 -58
  16. package/.husky/commit-msg +0 -4
  17. package/.husky/post-merge +0 -4
  18. package/.husky/pre-commit +0 -4
  19. package/.husky/pre-push +0 -4
  20. package/.prettierrc.js +0 -9
  21. package/CHANGELOG.md +0 -27
  22. package/CODE_OF_CONDUCT.md +0 -73
  23. package/CONTRIBUTING.md +0 -55
  24. package/SECURITY.md +0 -23
  25. package/__tests__/__fixtures__/test.marshall.js +0 -58
  26. package/__tests__/cli.parser.test.js +0 -121
  27. package/__tests__/cli.test.js +0 -110
  28. package/__tests__/cliPrompt.test.js +0 -409
  29. package/__tests__/env-var-integration.test.js +0 -109
  30. package/__tests__/marshalls.base.test.js +0 -113
  31. package/__tests__/marshalls.classMethods.test.js +0 -34
  32. package/__tests__/marshalls.expiredDomains.test.js +0 -100
  33. package/__tests__/marshalls.newBin.test.js +0 -441
  34. package/__tests__/marshalls.provenance.test.js +0 -383
  35. package/__tests__/marshalls.repo.test.js +0 -126
  36. package/__tests__/marshalls.signatures.test.js +0 -132
  37. package/__tests__/marshalls.snyk.test.js +0 -294
  38. package/__tests__/marshalls.tasks.test.js +0 -130
  39. package/__tests__/marshalls.typosquatting.test.js +0 -76
  40. package/__tests__/marshalls.version-maturity.test.js +0 -194
  41. package/__tests__/mocks/registryPackageOk.mock.json +0 -146
  42. package/__tests__/mocks/registryPackageUnpublished.mock.json +0 -31
  43. package/__tests__/packageManager.test.js +0 -93
  44. package/__tests__/packageRepoUtils.test.js +0 -251
  45. package/__tests__/reportResults.test.js +0 -772
  46. package/__tests__/scripts.test.js +0 -146
@@ -1,294 +0,0 @@
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
- })
@@ -1,130 +0,0 @@
1
- const path = require('path')
2
- const marshalls = require('../lib/marshalls')
3
-
4
- const PackageRepoUtilsMock = class Fake {
5
- getPackageInfo() {
6
- return Promise.resolve(true)
7
- }
8
- }
9
-
10
- test('running marshall tasks succeeds', async () => {
11
- marshalls.collectMarshalls = jest.fn(() => {
12
- return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
13
- })
14
-
15
- const config = {
16
- pkgs: [{ packageName: 'express' }, { packageName: 'semver' }],
17
- packageRepoUtils: new PackageRepoUtilsMock()
18
- }
19
-
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
- })
30
- })
31
-
32
- test('running marshall tasks fails', async () => {
33
- marshalls.collectMarshalls = jest.fn(() => {
34
- return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
35
- })
36
-
37
- const config = {
38
- pkgs: [{ packageName: 'express' }, { packageName: 'dockly' }],
39
- packageRepoUtils: new PackageRepoUtilsMock()
40
- }
41
-
42
- const mockedResults = [
43
- {
44
- 'test.marshall': {
45
- status: null,
46
- errors: [{ pkg: 'dockly', message: 'simulating mock error' }],
47
- warnings: [],
48
- data: { express: 'mock data check' }
49
- }
50
- }
51
- ]
52
-
53
- await expect(marshalls.tasks(config)).resolves.toMatchObject(mockedResults)
54
- })
55
-
56
- test('running marshall tasks includes an error when single package is not found', async () => {
57
- const PackageRepoUtilsNotFoundMock = class Fake {
58
- getPackageInfo() {
59
- return Promise.resolve({ error: 'Not found' })
60
- }
61
- }
62
-
63
- marshalls.collectMarshalls = jest.fn(() => {
64
- return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
65
- })
66
-
67
- const config = {
68
- pkgs: [{ packageName: 'nonexistent-package' }],
69
- packageRepoUtils: new PackageRepoUtilsNotFoundMock()
70
- }
71
-
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)
86
- })
87
-
88
- test('running marshall tasks filters out not found packages when multiple packages provided', async () => {
89
- const PackageRepoUtilsMixedMock = class Fake {
90
- getPackageInfo(packageName) {
91
- if (packageName === 'nonexistent-package') {
92
- return Promise.resolve({ error: 'Not found' })
93
- }
94
- return Promise.resolve(true)
95
- }
96
- }
97
-
98
- marshalls.collectMarshalls = jest.fn(() => {
99
- return Promise.resolve([path.join(process.cwd(), '__tests__/__fixtures__/test.marshall.js')])
100
- })
101
-
102
- const config = {
103
- pkgs: [
104
- { packageName: 'express' },
105
- { packageName: 'nonexistent-package' },
106
- { packageName: 'semver' }
107
- ],
108
- packageRepoUtils: new PackageRepoUtilsMixedMock()
109
- }
110
-
111
- const result = await marshalls.tasks(config)
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
- })
130
- })
@@ -1,76 +0,0 @@
1
- const TyposquattingMarshall = require('../lib/marshalls/typosquatting.marshall')
2
-
3
- describe('Typosquatting Marshall', () => {
4
- test('should remove duplicate entries from similar packages', async () => {
5
- const typosquattingMarshall = new TyposquattingMarshall({
6
- packageRepoUtils: {
7
- isPackageInAllowList: jest.fn(() => {
8
- return false // Simulate that the package is not in the allow list
9
- })
10
- }
11
- })
12
-
13
- // Create a test package that would match multiple similar packages
14
- const pkg = {
15
- packageName: 'ghtml' // This should match 'html' which appears multiple times in the data
16
- }
17
-
18
- try {
19
- await typosquattingMarshall.validate(pkg)
20
- // If no error is thrown, the test should fail
21
- expect(true).toBe(false)
22
- } catch (error) {
23
- // Check that the error message doesn't contain duplicate entries
24
- const errorMessage = error.message
25
- expect(errorMessage).toContain('Potential typosquatting with popular package(s):')
26
-
27
- // Extract the package names from the error message
28
- const packagesList = errorMessage.split('popular package(s): ')[1]
29
- const packages = packagesList.split(', ')
30
-
31
- // Check that there are no duplicates
32
- const uniquePackages = [...new Set(packages)]
33
- expect(packages.length).toBe(uniquePackages.length)
34
-
35
- // Verify that 'html' appears only once even though it exists multiple times in the data
36
- const htmlCount = packages.filter((pkg) => pkg === 'html').length
37
- expect(htmlCount).toBe(1)
38
- }
39
- })
40
-
41
- test('should not report typosquatting for packages in top packages list', async () => {
42
- const typosquattingMarshall = new TyposquattingMarshall({
43
- packageRepoUtils: {
44
- isPackageInAllowList: jest.fn(() => {
45
- return true
46
- })
47
- }
48
- })
49
-
50
- // Test with a package that exists in the top packages list
51
- const pkg = {
52
- packageName: 'express' // This should be in the top packages list
53
- }
54
-
55
- const result = await typosquattingMarshall.validate(pkg)
56
- expect(result).toEqual([])
57
- })
58
-
59
- test('should not report typosquatting for packages with no similar matches', async () => {
60
- const typosquattingMarshall = new TyposquattingMarshall({
61
- packageRepoUtils: {
62
- isPackageInAllowList: jest.fn(() => {
63
- return false // Simulate that the package is not in the allow list
64
- })
65
- }
66
- })
67
-
68
- // Test with a package that has no similar matches
69
- const pkg = {
70
- packageName: 'verylonganduniquenamethatdoesnotmatchanything'
71
- }
72
-
73
- const result = await typosquattingMarshall.validate(pkg)
74
- expect(result).toEqual([])
75
- })
76
- })
@@ -1,194 +0,0 @@
1
- 'use strict'
2
-
3
- const Marshall = require('../lib/marshalls/version-maturity.marshall')
4
-
5
- describe('Version Maturity Marshall', () => {
6
- test('should have the correct title', () => {
7
- const testMarshall = new Marshall({
8
- packageRepoUtils: null
9
- })
10
-
11
- expect(testMarshall.title()).toEqual('Checking version maturity')
12
- })
13
-
14
- test('should throw error for recently published version (same day)', async () => {
15
- const now = new Date()
16
- const hoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000) // 2 hours ago
17
-
18
- const testMarshall = new Marshall({
19
- packageRepoUtils: {
20
- getPackageInfo: () => {
21
- return Promise.resolve({
22
- time: {
23
- '1.0.0': hoursAgo.toISOString()
24
- }
25
- })
26
- },
27
- getSemVer: () => Promise.resolve('1.0.0')
28
- }
29
- })
30
-
31
- await expect(
32
- testMarshall.validate({
33
- packageName: 'test-package',
34
- packageVersion: '1.0.0'
35
- })
36
- ).rejects.toThrow(
37
- 'Detected a recently published version (published 2 hours ago) - consider waiting for community review'
38
- )
39
- })
40
-
41
- test('should throw error for recently published version (within threshold)', async () => {
42
- const now = new Date()
43
- const daysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000) // 3 days ago
44
-
45
- const testMarshall = new Marshall({
46
- packageRepoUtils: {
47
- getPackageInfo: () => {
48
- return Promise.resolve({
49
- time: {
50
- '1.0.0': daysAgo.toISOString()
51
- }
52
- })
53
- },
54
- getSemVer: () => Promise.resolve('1.0.0')
55
- }
56
- })
57
-
58
- await expect(
59
- testMarshall.validate({
60
- packageName: 'test-package',
61
- packageVersion: '1.0.0'
62
- })
63
- ).rejects.toThrow(
64
- 'Detected a recently published version (published 3 days ago) - consider waiting for community review'
65
- )
66
- })
67
-
68
- test('should throw error for version published exactly 1 day ago', async () => {
69
- const now = new Date()
70
- const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000) // 1 day ago
71
-
72
- const testMarshall = new Marshall({
73
- packageRepoUtils: {
74
- getPackageInfo: () => {
75
- return Promise.resolve({
76
- time: {
77
- '1.0.0': oneDayAgo.toISOString()
78
- }
79
- })
80
- },
81
- getSemVer: () => Promise.resolve('1.0.0')
82
- }
83
- })
84
-
85
- await expect(
86
- testMarshall.validate({
87
- packageName: 'test-package',
88
- packageVersion: '1.0.0'
89
- })
90
- ).rejects.toThrow(
91
- 'Detected a recently published version (published 1 day ago) - consider waiting for community review'
92
- )
93
- })
94
-
95
- test('should pass for version published beyond threshold', async () => {
96
- const now = new Date()
97
- const weekAgo = new Date(now.getTime() - 8 * 24 * 60 * 60 * 1000) // 8 days ago
98
-
99
- const testMarshall = new Marshall({
100
- packageRepoUtils: {
101
- getPackageInfo: () => {
102
- return Promise.resolve({
103
- time: {
104
- '1.0.0': weekAgo.toISOString()
105
- }
106
- })
107
- },
108
- getSemVer: () => Promise.resolve('1.0.0')
109
- }
110
- })
111
-
112
- const result = await testMarshall.validate({
113
- packageName: 'test-package',
114
- packageVersion: '1.0.0'
115
- })
116
-
117
- expect(result).toEqual({
118
- packageName: 'test-package',
119
- packageVersion: '1.0.0'
120
- })
121
- })
122
-
123
- test('should throw error when package time information is missing', async () => {
124
- const testMarshall = new Marshall({
125
- packageRepoUtils: {
126
- getPackageInfo: () => {
127
- return Promise.resolve({})
128
- },
129
- getSemVer: () => Promise.resolve('1.0.0')
130
- }
131
- })
132
-
133
- await expect(
134
- testMarshall.validate({
135
- packageName: 'test-package',
136
- packageVersion: '1.0.0'
137
- })
138
- ).rejects.toThrow('Could not determine package version information')
139
- })
140
-
141
- test('should throw error when version release date is missing', async () => {
142
- const testMarshall = new Marshall({
143
- packageRepoUtils: {
144
- getPackageInfo: () => {
145
- return Promise.resolve({
146
- time: {
147
- '2.0.0': new Date().toISOString()
148
- }
149
- })
150
- },
151
- getSemVer: () => Promise.resolve('1.0.0')
152
- }
153
- })
154
-
155
- await expect(
156
- testMarshall.validate({
157
- packageName: 'test-package',
158
- packageVersion: '1.0.0'
159
- })
160
- ).rejects.toThrow('Could not determine release date for version 1.0.0')
161
- })
162
-
163
- test('should handle version aliases correctly', async () => {
164
- const now = new Date()
165
- const daysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000) // 2 days ago
166
-
167
- const testMarshall = new Marshall({
168
- packageRepoUtils: {
169
- getPackageInfo: () => {
170
- return Promise.resolve({
171
- time: {
172
- '1.2.3': daysAgo.toISOString()
173
- }
174
- })
175
- },
176
- getSemVer: (packageName, packageVersion) => {
177
- if (packageVersion === 'latest') {
178
- return Promise.resolve('1.2.3')
179
- }
180
- return Promise.resolve(packageVersion)
181
- }
182
- }
183
- })
184
-
185
- await expect(
186
- testMarshall.validate({
187
- packageName: 'test-package',
188
- packageVersion: 'latest'
189
- })
190
- ).rejects.toThrow(
191
- 'Detected a recently published version (published 2 days ago) - consider waiting for community review'
192
- )
193
- })
194
- })