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