• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const t = require('tap')
2const Ajv = require('ajv')
3const { spdxOutput } = require('../../../lib/utils/sbom-spdx.js')
4
5t.cleanSnapshot = s => {
6  let sbom
7  try {
8    sbom = JSON.parse(s)
9  } catch (e) {
10    return s
11  }
12
13  sbom.documentNamespace = 'docns'
14
15  if (sbom.creationInfo) {
16    sbom.creationInfo.created = '2020-01-01T00:00:00.000Z'
17  }
18
19  return JSON.stringify(sbom, null, 2)
20}
21
22const npm = { version: '10.0.0 ' }
23
24const rootPkg = {
25  author: 'Author',
26}
27
28const root = {
29  packageName: 'root',
30  version: '1.0.0',
31  pkgid: 'root@1.0.0',
32  isRoot: true,
33  package: rootPkg,
34  location: '',
35  edgesOut: [],
36}
37
38const dep1 = {
39  packageName: 'dep1',
40  version: '0.0.1',
41  pkgid: 'dep1@0.0.1',
42  package: {},
43  location: 'node_modules/dep1',
44  edgesOut: [],
45}
46
47const dep2 = {
48  packageName: 'dep2',
49  version: '0.0.2',
50  pkgid: 'dep2@0.0.2',
51  package: {},
52  location: 'node_modules/dep2',
53  edgesOut: [],
54}
55
56const dep3 = {
57  packageName: 'dep3',
58  version: '0.0.3',
59  pkgid: 'dep3@0.0.3',
60  package: {},
61  location: 'node_modules/dep3',
62  edgesOut: [],
63}
64
65const dep5 = {
66  packageName: 'dep5',
67  version: '0.0.5',
68  pkgid: 'dep5@0.0.5',
69  package: {},
70  location: 'node_modules/dep5',
71  edgesOut: [],
72}
73
74const dep4 = {
75  packageName: 'dep4',
76  version: '0.0.4',
77  pkgid: 'npm@npm:dep4@0.0.4',
78  package: {},
79  location: 'dep4',
80  isWorkspace: true,
81  edgesOut: [{ to: dep5 }],
82}
83
84const dep4Link = {
85  packageName: 'dep4',
86  version: '0.0.4',
87  pkgid: 'dep4@0.0.4',
88  package: {},
89  location: 'node_modules/dep4',
90  isLink: true,
91  target: dep4,
92}
93
94dep4.linksIn = new Set([dep4Link])
95
96const dep6 = {
97  packageName: 'dep6',
98  version: '0.0.6',
99  pkgid: 'dep6@0.0.6',
100  extraneous: true,
101  package: {},
102  location: 'node_modules/dep6',
103  edgesOut: [],
104}
105
106t.test('single node - application package type', t => {
107  const res = spdxOutput({ npm, nodes: [root], packageType: 'application' })
108  t.matchSnapshot(JSON.stringify(res))
109  t.end()
110})
111
112t.test('single node - with single license', t => {
113  const pkg = { ...rootPkg, license: 'ISC' }
114  const node = { ...root, package: pkg }
115  const res = spdxOutput({ npm, nodes: [node] })
116  t.matchSnapshot(JSON.stringify(res))
117  t.end()
118})
119
120t.test('single node - with license object', t => {
121  const pkg = {
122    ...rootPkg,
123    license: {
124      type: 'MIT',
125      url: 'http://github.com/kriskowal/q/raw/master/LICENSE',
126    },
127  }
128  const node = { ...root, package: pkg }
129  const res = spdxOutput({ npm, nodes: [node] })
130  t.matchSnapshot(JSON.stringify(res))
131  t.end()
132})
133
134t.test('single node - with license expression', t => {
135  const pkg = { ...rootPkg, license: '(MIT OR Apache-2.0)' }
136  const node = { ...root, package: pkg }
137  const res = spdxOutput({ npm, nodes: [node] })
138  t.matchSnapshot(JSON.stringify(res))
139  t.end()
140})
141
142t.test('single node - with description', t => {
143  const pkg = { ...rootPkg, description: 'Package description' }
144  const node = { ...root, package: pkg }
145  const res = spdxOutput({ npm, nodes: [node] })
146  t.matchSnapshot(JSON.stringify(res))
147  t.end()
148})
149
150t.test('single node - with distribution url', t => {
151  const node = { ...root, resolved: 'https://registry.npmjs.org/root/-/root-1.0.0.tgz' }
152  const res = spdxOutput({ npm, nodes: [node] })
153  t.matchSnapshot(JSON.stringify(res))
154  t.end()
155})
156
157t.test('single node - with homepage', t => {
158  const pkg = { ...rootPkg, homepage: 'https://foo.bar/README.md' }
159  const node = { ...root, package: pkg }
160  const res = spdxOutput({ npm, nodes: [node] })
161  t.matchSnapshot(JSON.stringify(res))
162  t.end()
163})
164
165t.test('single node - with integrity', t => {
166  /* eslint-disable-next-line max-len */
167  const node = { ...root, integrity: 'sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==' }
168  const res = spdxOutput({ npm, nodes: [node] })
169  t.matchSnapshot(JSON.stringify(res))
170  t.end()
171})
172
173t.test('single node - from git url', t => {
174  const node = { ...root, type: 'git', resolved: 'https://github.com/foo/bar#1234' }
175  const res = spdxOutput({ npm, nodes: [node] })
176  t.matchSnapshot(JSON.stringify(res))
177  t.end()
178})
179
180t.test('single node - linked', t => {
181  const node = { ...root, isLink: true, target: { edgesOut: [] } }
182  const res = spdxOutput({ npm, nodes: [node] })
183  t.matchSnapshot(JSON.stringify(res))
184  t.end()
185})
186
187t.test('node - with deps', t => {
188  const node = { ...root,
189    edgesOut: [
190      { to: dep1, type: 'peer' },
191      { to: dep2, type: 'optional' },
192      { to: dep3, type: 'dev' },
193      { to: dep4 },
194      { to: undefined },
195      { to: { packageName: 'foo' } },
196    ] }
197  const res = spdxOutput({ npm, nodes: [node, dep1, dep2, dep3, dep4Link, dep4, dep5, dep6] })
198  t.matchSnapshot(JSON.stringify(res))
199  t.end()
200})
201
202// Check that all of the generated test snapshots validate against the SPDX schema
203t.test('schema validation', t => {
204  const ajv = new Ajv()
205
206  // Compile schema
207  const spdxSchema = require('../../schemas/spdx/spdx-2.3.schema.json')
208  const validate = ajv.compile(spdxSchema)
209
210  // Load snapshots for all tests in this file
211  const sboms = require('../../../tap-snapshots/test/lib/utils/sbom-spdx.js.test.cjs')
212
213  // Check that all snapshots validate against the SPDX schema
214  Object.entries(sboms).forEach(([name, sbom]) => {
215    t.ok(validate(JSON.parse(sbom)), { snapshot: name, error: validate.errors?.[0] })
216  })
217  t.end()
218})
219