From 7f4d98f11fd66dff558d17102c6b2dec2965b7c5 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:11:53 +0200 Subject: [PATCH 01/13] feat: parse "dist" field in package.json Signed-off-by: Jan Kowalleck --- HISTORY.md | 8 ++ src/_helpers/packageJson.ts | 1 + src/factories/fromNodePackageJson.node.ts | 47 +++++++++--- src/index.common.ts | 1 - src/index.node.ts | 1 + src/index.web.ts | 1 + src/utils/{index.ts => index.common.ts} | 0 src/utils/index.node.ts | 26 +++++++ src/utils/index.web.ts | 26 +++++++ src/utils/npmjs.node.ts | 89 +++++++++++++++++++++++ 10 files changed, 190 insertions(+), 10 deletions(-) rename src/utils/{index.ts => index.common.ts} (100%) create mode 100644 src/utils/index.node.ts create mode 100644 src/utils/index.web.ts create mode 100644 src/utils/npmjs.node.ts diff --git a/HISTORY.md b/HISTORY.md index 62b2ff17f..dd7a906f6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,6 +6,14 @@ All notable changes to this project will be documented in this file. +* Added + * `factories.FromNodePackageJson.makeExternalReferences` supports "dist" field (via [#]) + * New symbols under `utils.NpmJs` (via [#]) + * `defaultRepoMatcher` + * `parsePackageIntegrity` + +[#]: + ## 8.1.0 -- 2025-06-04 Support for _Node.js_ v24. diff --git a/src/_helpers/packageJson.ts b/src/_helpers/packageJson.ts index 056b06464..c7bdf6554 100644 --- a/src/_helpers/packageJson.ts +++ b/src/_helpers/packageJson.ts @@ -62,4 +62,5 @@ export interface PackageJson { directory?: string } // ... to be continued + dist?: any // see https://github.com/CycloneDX/cyclonedx-node-npm/issues/1300 } diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index 1d32899e8..ffedbdcb1 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -29,12 +29,15 @@ Copyright (c) OWASP Foundation. All Rights Reserved. import type { PackageURL } from 'packageurl-js' import { PurlQualifierNames } from 'packageurl-js' -import {tryCanonicalizeGitUrl} from "../_helpers/gitUrl" +import { tryCanonicalizeGitUrl } from "../_helpers/gitUrl" import { isNotUndefined } from '../_helpers/notUndefined' import type { PackageJson } from '../_helpers/packageJson' import { ExternalReferenceType } from '../enums/externalReferenceType' +import { HashAlgorithm } from "../enums/hashAlogorithm"; import type { Component } from '../models/component' import { ExternalReference } from '../models/externalReference' +import { HashDictionary } from '../models/hash' +import { defaultRepoMatcher, parsePackageIntegrity } from '../utils/npmjs.node' import { PackageUrlFactory as PlainPackageUrlFactory } from './packageUrl' /** @@ -47,6 +50,7 @@ export class ExternalReferenceFactory { try { refs.push(this.makeVcs(data)) } catch { /* pass */ } try { refs.push(this.makeHomepage(data)) } catch { /* pass */ } try { refs.push(this.makeIssueTracker(data)) } catch { /* pass */ } + try { refs.push(this.makeDist(data)) } catch { /* pass */ } return refs.filter(isNotUndefined) } @@ -100,13 +104,38 @@ export class ExternalReferenceFactory { ? new ExternalReference(url, ExternalReferenceType.IssueTracker, { comment }) : undefined } -} -/** - * The default repository is `https://registry.npmjs.org`. - * @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm} - */ -const npmDefaultRepositoryMatcher = /^https?:\/\/registry\.npmjs\.org(:?\/|$)/ + makeDist(data: PackageJson): ExternalReference | undefined { + // "dist" might be used in bundled dependencies' manifests. + // docs: https://blog.npmjs.org/post/172999548390/new-pgp-machinery + /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- acknowledged */ + const { tarball, integrity, shasum } = data.dist ?? {} + if (typeof tarball === 'string') { + const hashes = new HashDictionary() + const hashSources = [] + if (typeof integrity === 'string') { + try { + hashes.set(...parsePackageIntegrity(integrity)) + hashSources.push(' and property "dist.integrity"') + } catch { /* pass */ + } + } + if (typeof shasum === 'string' && shasum.length === 40) { + hashes.set(HashAlgorithm["SHA-1"], shasum) + hashSources.push(' and property "dist.shasum"') + } + return new ExternalReference( + tarball, + ExternalReferenceType.Distribution, + { + hashes, + comment: `as detected from npm-ls property "dist.tarball"${hashSources.join('')}` + } + ) + } + return undefined + } +} /** * Node-specific PackageUrlFactory. @@ -134,13 +163,13 @@ export class PackageUrlFactory extends PlainPackageUrlFactory<'npm'> { * - "download_url" is stripped, if it is NPM's default registry ("registry.npmjs.org") * - "checksum" is stripped, unless a "download_url" or "vcs_url" is given. */ - #finalizeQualifiers (purl: PackageURL): PackageURL { + #finalizeQualifiers(purl: PackageURL): PackageURL { const qualifiers = new Map(Object.entries(purl.qualifiers ?? {})) const downloadUrl = qualifiers.get(PurlQualifierNames.DownloadUrl) if (downloadUrl !== undefined) { qualifiers.delete(PurlQualifierNames.VcsUrl) - if (npmDefaultRepositoryMatcher.test(downloadUrl)) { + if (defaultRepoMatcher.test(downloadUrl)) { qualifiers.delete(PurlQualifierNames.DownloadUrl) } } diff --git a/src/index.common.ts b/src/index.common.ts index 1012bbddd..3c9dc9913 100644 --- a/src/index.common.ts +++ b/src/index.common.ts @@ -22,5 +22,4 @@ export * as Models from './models' export * as SPDX from './spdx' export * as Spec from './spec' export * as Types from './types' -export * as Utils from './utils' // do not export the _helpers, they are for internal use only diff --git a/src/index.node.ts b/src/index.node.ts index 7122afd83..cace8af4e 100644 --- a/src/index.node.ts +++ b/src/index.node.ts @@ -28,6 +28,7 @@ export * from './index.common' export * as Builders from './builders/index.node' export * as Factories from './factories/index.node' export * as Serialize from './serialize/index.node' +export * as Utils from './utils/index.node' export * as Validation from './validation/index.node' /** diff --git a/src/index.web.ts b/src/index.web.ts index 48de0863a..f4efdf3d6 100644 --- a/src/index.web.ts +++ b/src/index.web.ts @@ -23,6 +23,7 @@ export * from './index.common' export * as Factories from './factories/index.web' export * as Serialize from './serialize/index.web' +export * as Utils from './utils/index.web' export * as Validation from './validation/index.web' // endregion web-specifics diff --git a/src/utils/index.ts b/src/utils/index.common.ts similarity index 100% rename from src/utils/index.ts rename to src/utils/index.common.ts diff --git a/src/utils/index.node.ts b/src/utils/index.node.ts new file mode 100644 index 000000000..f12f89438 --- /dev/null +++ b/src/utils/index.node.ts @@ -0,0 +1,26 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +export * from './index.common' + +// region node-specifics + +export * as NpmJs from './npmjs.node' + +// endregion node-specifics diff --git a/src/utils/index.web.ts b/src/utils/index.web.ts new file mode 100644 index 000000000..b2beefadf --- /dev/null +++ b/src/utils/index.web.ts @@ -0,0 +1,26 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +export * from './index.common' + +// region web-specifics + +// ... nothing yet + +// endregion web-specifics diff --git a/src/utils/npmjs.node.ts b/src/utils/npmjs.node.ts new file mode 100644 index 000000000..fb48b6fb1 --- /dev/null +++ b/src/utils/npmjs.node.ts @@ -0,0 +1,89 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +import {HashAlgorithm} from '../enums/hashAlogorithm' + +/** + * See {@link https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages | package lock docs} for "integrity". + * See {@link https://blog.npmjs.org/post/172999548390/new-pgp-machinery | new pgp machinery} for "integrity". + * + * integrity: A sha512 or sha1 [Standard Subresource Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) string for the artifact that was unpacked in this location. + */ +const integrityRE: ReadonlyMap = new Map([ + // !!! this list is pre-sorted, starting with most-common usage. + + /* base64 alphabet: `A-Za-z0-9+/` and `=` for padding + * SHA-512 => base64 over 512 bit => 86 chars + 2 chars padding. + * examples: + * - sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== + * - sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA== + * - sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA== + */ + [HashAlgorithm['SHA-512'], /^sha512-([a-z0-9+/]{86}==)$/i], + + /* base64 alphabet: `A-Za-z0-9+/` and `=` for padding + * SHA-1 => base64 over 160 bit => 27 chars + 1 chars padding. + * examples: + * - sha1-aSbRsZT7xze47tUTdW3i/Np+pAg= + * - sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0= + * - sha1-XV8g50dxuFICXD7bZslGLuuRPQM= + */ + [HashAlgorithm['SHA-1'], /^sha1-([a-z0-9+/]{27}=)$/i], + + /* base64 alphabet: `A-Za-z0-9+/` and `=` for padding + * SHA-256 => base64 over 256 bit => 43 chars + 1 chars padding. + * examples: + * - sha256-jxzgcB+8dLn7Cjjyg7stGWMftZf6rbdvgoE85TOzmT4= + * - sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= + * - sha256-+8Gp+Fjqnhd5FpZL2Iw9N7kaHoRBJ2XimVB3fyZcS3U= + */ + [HashAlgorithm['SHA-256'], /^sha256-([a-z0-9+/]{43}=)$/i], + + /* base64 alphabet: `A-Za-z0-9+/` and `=` for padding + * SHA-384 => base64 over 384 bit => 64 chars + 0 chars padding. + * example: + * - sha384-aDkxLz2zQ0dwcNPAsr7NQXs1cVTUh5TQHXjPtGF+1auBmne2gy9lQt0Yu3OBMe9+ + * - sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC + * - sha384-/b2OdaZ/KfcBpOBAOF4uI5hjA+oQI5IRr5B/y7g1eLPkF8txzmRu/QgZ3YwIjeG9 + */ + [HashAlgorithm['SHA-384'], /^sha384-([a-z0-9+/]{64})$/i] +]) + +/** + * @throws {@link RangeError} if value is unparsable + */ +export function parsePackageIntegrity (integrity: string): [HashAlgorithm, string] { + for (const [hashAlgorithm, hashRE] of integrityRE) { + const hashMatchBase64 = hashRE.exec(integrity) ?? [] + if (hashMatchBase64.length === 2) { + return [ + hashAlgorithm, + Buffer.from(hashMatchBase64[1], 'base64').toString('hex') + ] + } + } + throw new RangeError('unparsable value') +} + + +/** + * The default repository is `https://registry.npmjs.org`. + * @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm} + */ +export const defaultRepoMatcher = /^https?:\/\/registry\.npmjs\.org(:?\/|$)/ From 3adbb6be058e744037d3fb5d5079bb24818f9156 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:16:02 +0200 Subject: [PATCH 02/13] chore: adjust exports Signed-off-by: Jan Kowalleck --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b89b056c3..0f091fb5c 100644 --- a/package.json +++ b/package.json @@ -149,8 +149,8 @@ "default": "./dist.node/types/index.js" }, "./Utils": { - "types": "./dist.d/utils/index.d.ts", - "default": "./dist.node/utils/index.js" + "types": "./dist.d/utils/index.node.d.ts", + "default": "./dist.node/utils/index.node.js" }, "./Validation": { "types": "./dist.d/validation/index.node.d.ts", From cc88ffc5f78a3717012480f84ab0aa6a8b580cf2 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:18:48 +0200 Subject: [PATCH 03/13] docs Signed-off-by: Jan Kowalleck --- HISTORY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index dd7a906f6..64343e2e3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,12 +7,12 @@ All notable changes to this project will be documented in this file. * Added - * `factories.FromNodePackageJson.makeExternalReferences` supports "dist" field (via [#]) - * New symbols under `utils.NpmJs` (via [#]) + * `factories.FromNodePackageJson.makeExternalReferences` supports "dist" field (via [#1246]) + * New symbols under `utils.NpmJs` (via [#1246]) * `defaultRepoMatcher` * `parsePackageIntegrity` -[#]: +[#1246]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1246 ## 8.1.0 -- 2025-06-04 From 459823e3ef79f4457961dfc91836a7881c048dbc Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:39:22 +0200 Subject: [PATCH 04/13] docs Signed-off-by: Jan Kowalleck --- src/factories/fromNodePackageJson.node.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index ffedbdcb1..2ed35e3e9 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -115,6 +115,8 @@ export class ExternalReferenceFactory { const hashSources = [] if (typeof integrity === 'string') { try { + // actually not the hash of the file, but more of an integrity-check -- lets use it anyway. + // see https://blog.npmjs.org/post/172999548390/new-pgp-machinery hashes.set(...parsePackageIntegrity(integrity)) hashSources.push(' and property "dist.integrity"') } catch { /* pass */ From 74a39d86151894edff3307780741ac4ff2fe0f2b Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:40:29 +0200 Subject: [PATCH 05/13] docs Signed-off-by: Jan Kowalleck --- src/factories/fromNodePackageJson.node.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index 2ed35e3e9..c9a79893d 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -119,8 +119,7 @@ export class ExternalReferenceFactory { // see https://blog.npmjs.org/post/172999548390/new-pgp-machinery hashes.set(...parsePackageIntegrity(integrity)) hashSources.push(' and property "dist.integrity"') - } catch { /* pass */ - } + } catch { /* pass */ } } if (typeof shasum === 'string' && shasum.length === 40) { hashes.set(HashAlgorithm["SHA-1"], shasum) @@ -131,7 +130,7 @@ export class ExternalReferenceFactory { ExternalReferenceType.Distribution, { hashes, - comment: `as detected from npm-ls property "dist.tarball"${hashSources.join('')}` + comment: `as detected from manifest property "dist.tarball"${hashSources.join('')}` } ) } From 1470b24d142aa2aa35aff3ec882e96a697e77606 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 14:40:54 +0200 Subject: [PATCH 06/13] docs Signed-off-by: Jan Kowalleck --- src/factories/fromNodePackageJson.node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index c9a79893d..561a94a41 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -130,7 +130,7 @@ export class ExternalReferenceFactory { ExternalReferenceType.Distribution, { hashes, - comment: `as detected from manifest property "dist.tarball"${hashSources.join('')}` + comment: `as detected from PackageJson property "dist.tarball"${hashSources.join('')}` } ) } From f8825d390e27398d937b53f490715bccde02db13 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 15:12:02 +0200 Subject: [PATCH 07/13] tests Signed-off-by: Jan Kowalleck --- HISTORY.md | 6 +- src/factories/fromNodePackageJson.node.ts | 4 +- src/utils/index.node.ts | 2 +- .../{npmjs.node.ts => npmjsUtility.node.ts} | 4 +- tests/unit/Utils.NpmjsUtility.spec.js | 57 +++++++++++++++++++ 5 files changed, 65 insertions(+), 8 deletions(-) rename src/utils/{npmjs.node.ts => npmjsUtility.node.ts} (96%) create mode 100644 tests/unit/Utils.NpmjsUtility.spec.js diff --git a/HISTORY.md b/HISTORY.md index 64343e2e3..3097f5dd9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,9 +7,9 @@ All notable changes to this project will be documented in this file. * Added - * `factories.FromNodePackageJson.makeExternalReferences` supports "dist" field (via [#1246]) - * New symbols under `utils.NpmJs` (via [#1246]) - * `defaultRepoMatcher` + * `factories.FromNodePackageJson.makeExternalReferences()` supports "dist" field (via [#1246]) + * New symbols under `utils.NpmjsUtility` (via [#1246]) + * `defaultRegistryMatcher` * `parsePackageIntegrity` [#1246]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1246 diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index 561a94a41..99b7818e4 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -37,7 +37,7 @@ import { HashAlgorithm } from "../enums/hashAlogorithm"; import type { Component } from '../models/component' import { ExternalReference } from '../models/externalReference' import { HashDictionary } from '../models/hash' -import { defaultRepoMatcher, parsePackageIntegrity } from '../utils/npmjs.node' +import { defaultRegistryMatcher, parsePackageIntegrity } from '../utils/npmjsUtility.node' import { PackageUrlFactory as PlainPackageUrlFactory } from './packageUrl' /** @@ -170,7 +170,7 @@ export class PackageUrlFactory extends PlainPackageUrlFactory<'npm'> { const downloadUrl = qualifiers.get(PurlQualifierNames.DownloadUrl) if (downloadUrl !== undefined) { qualifiers.delete(PurlQualifierNames.VcsUrl) - if (defaultRepoMatcher.test(downloadUrl)) { + if (defaultRegistryMatcher.test(downloadUrl)) { qualifiers.delete(PurlQualifierNames.DownloadUrl) } } diff --git a/src/utils/index.node.ts b/src/utils/index.node.ts index f12f89438..48df09c1f 100644 --- a/src/utils/index.node.ts +++ b/src/utils/index.node.ts @@ -21,6 +21,6 @@ export * from './index.common' // region node-specifics -export * as NpmJs from './npmjs.node' +export * as NpmjsUtility from './npmjsUtility.node' // endregion node-specifics diff --git a/src/utils/npmjs.node.ts b/src/utils/npmjsUtility.node.ts similarity index 96% rename from src/utils/npmjs.node.ts rename to src/utils/npmjsUtility.node.ts index fb48b6fb1..14208cf0b 100644 --- a/src/utils/npmjs.node.ts +++ b/src/utils/npmjsUtility.node.ts @@ -83,7 +83,7 @@ export function parsePackageIntegrity (integrity: string): [HashAlgorithm, strin /** - * The default repository is `https://registry.npmjs.org`. + * The default registry is `https://registry.npmjs.org`. * @see {@link https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm} */ -export const defaultRepoMatcher = /^https?:\/\/registry\.npmjs\.org(:?\/|$)/ +export const defaultRegistryMatcher = /^https?:\/\/registry\.npmjs\.org(:?\/|$)/ diff --git a/tests/unit/Utils.NpmjsUtility.spec.js b/tests/unit/Utils.NpmjsUtility.spec.js new file mode 100644 index 000000000..9cd2b7c00 --- /dev/null +++ b/tests/unit/Utils.NpmjsUtility.spec.js @@ -0,0 +1,57 @@ +/*! +This file is part of CycloneDX JavaScript Library. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +SPDX-License-Identifier: Apache-2.0 +Copyright (c) OWASP Foundation. All Rights Reserved. +*/ + +const assert = require('node:assert') + +const { suite, test } = require('mocha') + +const { + Utils: { + NpmjsUtility + } +} = require('../../') + +suite('unit: Utils.NpmjsUtility', () => { + suite('defaultRegistryMatcher', () => { + test('matches pure domain', () => { + const match = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org') + assert.strictEqual(match, true) + }) + test('matches with path', () => { + const match = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org/foo/bar') + assert.strictEqual(match, true) + }) + suite('not match unexpected', () => { + for (const c in [ + 'https://my-own=registry.local', + 'https://registry.npmjs.org.uk', + 'https://registry.npmjs.org.uk/foo/bar' + ]) { + test(c, () => { + const match = NpmjsUtility.defaultRegistryMatcher.test(c) + assert.strictEqual(match, false) + }) + } + }) + }) + + suite('parsePackageIntegrity', () => { + + }) +}) From d2f227de7a2bc581a4585856deb479951c8e6514 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 15:41:44 +0200 Subject: [PATCH 08/13] tests Signed-off-by: Jan Kowalleck --- tests/unit/Utils.NpmjsUtility.spec.js | 56 +++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/tests/unit/Utils.NpmjsUtility.spec.js b/tests/unit/Utils.NpmjsUtility.spec.js index 9cd2b7c00..0de65a521 100644 --- a/tests/unit/Utils.NpmjsUtility.spec.js +++ b/tests/unit/Utils.NpmjsUtility.spec.js @@ -22,6 +22,9 @@ const assert = require('node:assert') const { suite, test } = require('mocha') const { + Enums: { + HashAlgorithm + }, Utils: { NpmjsUtility } @@ -30,28 +33,65 @@ const { suite('unit: Utils.NpmjsUtility', () => { suite('defaultRegistryMatcher', () => { test('matches pure domain', () => { - const match = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org') - assert.strictEqual(match, true) + const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org') + assert.strictEqual(actual, true) }) test('matches with path', () => { - const match = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org/foo/bar') - assert.strictEqual(match, true) + const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org/foo/bar') + assert.strictEqual(actual, true) }) suite('not match unexpected', () => { - for (const c in [ + for (const c of [ 'https://my-own=registry.local', 'https://registry.npmjs.org.uk', 'https://registry.npmjs.org.uk/foo/bar' ]) { test(c, () => { - const match = NpmjsUtility.defaultRegistryMatcher.test(c) - assert.strictEqual(match, false) + const actual = NpmjsUtility.defaultRegistryMatcher.test(c) + assert.strictEqual(actual, false) }) } }) }) suite('parsePackageIntegrity', () => { - + suite('as expected', () => { + for (const [c, ...expected] of [ + ['sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==', + HashAlgorithm['SHA-512'], + 'cef8fae53905788b778ba6a3e5b22f243ce38d0406b38a3fb0da1ece0d45d6461aa7d36eda3714769c334522531cc3331b41fb6d9927e2b350a489a1bb1597d8' + ], + ['sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', + HashAlgorithm['SHA-1'], + '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed' + ], + ['sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', + HashAlgorithm['SHA-256'], + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ], + ['sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC', + HashAlgorithm['SHA-384'], + 'a2a56e01f5d129aa7b7dd81c098e6eca433af91f46a90f0afeec72f6bc7b1cd42519897590fcd0868d70c7827063cc02' + ], + ]) { + test(c, () => { + const actual = NpmjsUtility.parsePackageIntegrity(c) + assert.deepStrictEqual(actual, expected) + }) + } + }) + suite('fails', () => { + for (const c of [ + 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0', // missing character + 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0==', // additional character + 'sha512-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', // alg and hash dont match + ]) { + test(c, () => { + assert.throws(() => { + NpmjsUtility.parsePackageIntegrity(c) + }) + }) + } + }) }) }) From 194b27a6bba9834d9e290d94ca450d6d0b65329a Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 15:51:17 +0200 Subject: [PATCH 09/13] tests Signed-off-by: Jan Kowalleck --- ...s.FromNodePackageJson.PackageUrlFactory.js | 12 +- tests/unit/Utils.NpmjsUtility.spec.js | 118 +++++++++--------- 2 files changed, 67 insertions(+), 63 deletions(-) diff --git a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js index 411959584..0c461aba1 100644 --- a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js +++ b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js @@ -22,11 +22,17 @@ const assert = require('node:assert') const { suite, test } = require('mocha') const { - Factories: { FromNodePackageJson: { PackageUrlFactory } }, + Factories: { FromNodePackageJson: { + ExternalReferenceFactory, PackageUrlFactory + }}, Enums: { ComponentType, ExternalReferenceType }, Models: { Component, ExternalReference, ExternalReferenceRepository } } = require('../../') +suite('unit: Factories.FromNodePackageJson.ExternalReferenceFactory', () => { + // TODO +}) + suite('unit: Factories.FromNodePackageJson.PackageUrlFactory', () => { suite('makeFromComponent()', () => { test('plain', () => { @@ -36,7 +42,7 @@ suite('unit: Factories.FromNodePackageJson.PackageUrlFactory', () => { assert.deepEqual(actual, 'TODO') }) - test('strips default repo', () => { + test('strips default registry from qualifiers', () => { // see https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#npm const component = new Component(ComponentType.Library, 'testing', { externalReferences: new ExternalReferenceRepository([ @@ -58,7 +64,7 @@ suite('unit: Factories.FromNodePackageJson.PackageUrlFactory', () => { }) }) - test('dont strip BA repo', () => { + test('dont strip BA registry from qualifiers', () => { // regression test for https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1073 const component = new Component(ComponentType.Library, 'testing', { externalReferences: new ExternalReferenceRepository([ diff --git a/tests/unit/Utils.NpmjsUtility.spec.js b/tests/unit/Utils.NpmjsUtility.spec.js index 0de65a521..b05cca8ba 100644 --- a/tests/unit/Utils.NpmjsUtility.spec.js +++ b/tests/unit/Utils.NpmjsUtility.spec.js @@ -30,68 +30,66 @@ const { } } = require('../../') -suite('unit: Utils.NpmjsUtility', () => { - suite('defaultRegistryMatcher', () => { - test('matches pure domain', () => { - const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org') - assert.strictEqual(actual, true) - }) - test('matches with path', () => { - const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org/foo/bar') - assert.strictEqual(actual, true) - }) - suite('not match unexpected', () => { - for (const c of [ - 'https://my-own=registry.local', - 'https://registry.npmjs.org.uk', - 'https://registry.npmjs.org.uk/foo/bar' - ]) { - test(c, () => { - const actual = NpmjsUtility.defaultRegistryMatcher.test(c) - assert.strictEqual(actual, false) - }) - } - }) +suite('unit: Utils.NpmjsUtility.defaultRegistryMatcher', () => { + test('matches pure domain', () => { + const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org') + assert.strictEqual(actual, true) + }) + test('matches with path', () => { + const actual = NpmjsUtility.defaultRegistryMatcher.test('https://registry.npmjs.org/foo/bar') + assert.strictEqual(actual, true) + }) + suite('not match unexpected', () => { + for (const c of [ + 'https://my-own=registry.local', + 'https://registry.npmjs.org.uk', + 'https://registry.npmjs.org.uk/foo/bar' + ]) { + test(c, () => { + const actual = NpmjsUtility.defaultRegistryMatcher.test(c) + assert.strictEqual(actual, false) + }) + } }) +}) - suite('parsePackageIntegrity', () => { - suite('as expected', () => { - for (const [c, ...expected] of [ - ['sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==', - HashAlgorithm['SHA-512'], - 'cef8fae53905788b778ba6a3e5b22f243ce38d0406b38a3fb0da1ece0d45d6461aa7d36eda3714769c334522531cc3331b41fb6d9927e2b350a489a1bb1597d8' - ], - ['sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', - HashAlgorithm['SHA-1'], - '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed' - ], - ['sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', - HashAlgorithm['SHA-256'], - 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' - ], - ['sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC', - HashAlgorithm['SHA-384'], - 'a2a56e01f5d129aa7b7dd81c098e6eca433af91f46a90f0afeec72f6bc7b1cd42519897590fcd0868d70c7827063cc02' - ], - ]) { - test(c, () => { - const actual = NpmjsUtility.parsePackageIntegrity(c) - assert.deepStrictEqual(actual, expected) - }) - } - }) - suite('fails', () => { - for (const c of [ - 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0', // missing character - 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0==', // additional character - 'sha512-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', // alg and hash dont match - ]) { - test(c, () => { - assert.throws(() => { - NpmjsUtility.parsePackageIntegrity(c) - }) +suite('unit: Utils.NpmjsUtility.parsePackageIntegrity', () => { + suite('as expected', () => { + for (const [c, ...expected] of [ + ['sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==', + HashAlgorithm['SHA-512'], + 'cef8fae53905788b778ba6a3e5b22f243ce38d0406b38a3fb0da1ece0d45d6461aa7d36eda3714769c334522531cc3331b41fb6d9927e2b350a489a1bb1597d8' + ], + ['sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', + HashAlgorithm['SHA-1'], + '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed' + ], + ['sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=', + HashAlgorithm['SHA-256'], + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ], + ['sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC', + HashAlgorithm['SHA-384'], + 'a2a56e01f5d129aa7b7dd81c098e6eca433af91f46a90f0afeec72f6bc7b1cd42519897590fcd0868d70c7827063cc02' + ], + ]) { + test(c, () => { + const actual = NpmjsUtility.parsePackageIntegrity(c) + assert.deepStrictEqual(actual, expected) + }) + } + }) + suite('fails', () => { + for (const c of [ + 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0', // missing character + 'sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0==', // additional character + 'sha512-Kq5sNclPz7QV2+lfQIuc6R7oRu0=', // alg and hash dont match + ]) { + test(c, () => { + assert.throws(() => { + NpmjsUtility.parsePackageIntegrity(c) }) - } - }) + }) + } }) }) From bfa43f0457511166467e086ce598524c41b05b3a Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 16:10:45 +0200 Subject: [PATCH 10/13] docs Signed-off-by: Jan Kowalleck --- HISTORY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 3097f5dd9..b3180df9b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,12 +7,13 @@ All notable changes to this project will be documented in this file. * Added - * `factories.FromNodePackageJson.makeExternalReferences()` supports "dist" field (via [#1246]) + * `factories.FromNodePackageJson.makeExternalReferences()` supports "dist" field ([#1247] via [#1246]) * New symbols under `utils.NpmjsUtility` (via [#1246]) * `defaultRegistryMatcher` * `parsePackageIntegrity` [#1246]: https://github.com/CycloneDX/cyclonedx-javascript-library/pull/1246 +[#1247]: https://github.com/CycloneDX/cyclonedx-javascript-library/issues/1247 ## 8.1.0 -- 2025-06-04 From ded410cb0dfa9858a1d1ae54d8d22efc7d13f858 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 16:40:34 +0200 Subject: [PATCH 11/13] tests Signed-off-by: Jan Kowalleck --- src/factories/fromNodePackageJson.node.ts | 15 +++----- ...ckageJson.ExternalReferenceFactory.test.js | 35 +++++++++++++++++++ ...s.FromNodePackageJson.PackageUrlFactory.js | 8 +---- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/factories/fromNodePackageJson.node.ts b/src/factories/fromNodePackageJson.node.ts index 99b7818e4..80073b66d 100644 --- a/src/factories/fromNodePackageJson.node.ts +++ b/src/factories/fromNodePackageJson.node.ts @@ -112,27 +112,20 @@ export class ExternalReferenceFactory { const { tarball, integrity, shasum } = data.dist ?? {} if (typeof tarball === 'string') { const hashes = new HashDictionary() - const hashSources = [] + let comment = 'as detected from PackageJson property "dist.tarball"' if (typeof integrity === 'string') { try { // actually not the hash of the file, but more of an integrity-check -- lets use it anyway. // see https://blog.npmjs.org/post/172999548390/new-pgp-machinery hashes.set(...parsePackageIntegrity(integrity)) - hashSources.push(' and property "dist.integrity"') + comment += ' and property "dist.integrity"' } catch { /* pass */ } } if (typeof shasum === 'string' && shasum.length === 40) { hashes.set(HashAlgorithm["SHA-1"], shasum) - hashSources.push(' and property "dist.shasum"') + comment += ' and property "dist.shasum"' } - return new ExternalReference( - tarball, - ExternalReferenceType.Distribution, - { - hashes, - comment: `as detected from PackageJson property "dist.tarball"${hashSources.join('')}` - } - ) + return new ExternalReference(tarball, ExternalReferenceType.Distribution, { hashes, comment }) } return undefined } diff --git a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js index e2d53c252..7816f838f 100644 --- a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js +++ b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js @@ -97,6 +97,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () assert.strictEqual(actual.length, 0) }) }) + suite('from "repository"', () => { test('non-empty string', () => { const expected = [new ExternalReference( @@ -267,4 +268,38 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () assert.deepEqual(actual, expected) }) }) + + +}) + +suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () => { + const factory = new ExternalReferenceFactory() + + const cases = { + makeVcs: [ + // TODO + ], + makeHomepage: [ + // TODO + ], + makeIssueTracker: [ + // TODO + ], + makeDist: [ + // TODO + ], + } + cases.makeExternalReferences = [ + // TODO + ] + for (const [func, testData] of Object.entries(cases)) { + suite(func, () => { + for (const [label, data, expected] of testData) { + test(label, () => { + const actual = factory[func](data) + assert.deepStrictEqual(actual, expected) + }) + } + }) + } }) diff --git a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js index 0c461aba1..dcacee92e 100644 --- a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js +++ b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js @@ -22,17 +22,11 @@ const assert = require('node:assert') const { suite, test } = require('mocha') const { - Factories: { FromNodePackageJson: { - ExternalReferenceFactory, PackageUrlFactory - }}, + Factories: { FromNodePackageJson: { PackageUrlFactory }}, Enums: { ComponentType, ExternalReferenceType }, Models: { Component, ExternalReference, ExternalReferenceRepository } } = require('../../') -suite('unit: Factories.FromNodePackageJson.ExternalReferenceFactory', () => { - // TODO -}) - suite('unit: Factories.FromNodePackageJson.PackageUrlFactory', () => { suite('makeFromComponent()', () => { test('plain', () => { From 2998f34ea1d62186a93fc37f44203d35ef841715 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 17:03:52 +0200 Subject: [PATCH 12/13] tests Signed-off-by: Jan Kowalleck --- ...ckageJson.ExternalReferenceFactory.test.js | 98 ++++++++++++++----- ...s.FromNodePackageJson.PackageUrlFactory.js | 2 +- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js index 7816f838f..9aaeec946 100644 --- a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js +++ b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js @@ -22,8 +22,8 @@ const assert = require('node:assert') const { suite, test } = require('mocha') const { - Enums: { ExternalReferenceType }, - Models: { ExternalReference }, + Enums: { ExternalReferenceType, HashAlgorithm }, + Models: { ExternalReference, HashDictionary }, Factories: { FromNodePackageJson: { ExternalReferenceFactory } } } = require('../../') @@ -39,7 +39,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { homepage: 'https://example.com' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('is empty string', () => { const data = { homepage: '' } @@ -62,7 +62,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { bugs: 'https://example.com' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('is empty string', () => { const data = { bugs: '' } @@ -84,7 +84,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { bugs: { url: 'https://example.com' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('is empty string', () => { const data = { bugs: { url: '' } } @@ -107,7 +107,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: '../foo/bar' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-git-url', () => { const expected = [new ExternalReference( @@ -117,7 +117,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: 'git@example.com:foo/bar' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('explicit-git-url', () => { const expected = [new ExternalReference( @@ -127,7 +127,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: 'git+https://example.com/dings.git' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-svn-url', () => { const expected = [new ExternalReference( @@ -137,19 +137,19 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: 'svn://example.com/foo/trunk' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('empty string', () => { const expected = [] const data = { repository: '' } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('undefined', () => { const expected = [] const data = { } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) }) suite('from "repository.url"', () => { @@ -161,7 +161,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: '../foo/bar' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-git-url', () => { const expected = [new ExternalReference( @@ -171,7 +171,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'git@example.com:foo/bar' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('explicit-git-url', () => { const expected = [new ExternalReference( @@ -181,7 +181,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'git+https://example.com/dings.git' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-svn-url', () => { const expected = [new ExternalReference( @@ -191,19 +191,19 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'svn://example.com/foo/trunk' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('empty string', () => { const expected = [] const data = { repository: { url: '' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('undefined', () => { const expected = [] const data = { repository: { } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) }) suite('from "repository.directory"', () => { @@ -215,7 +215,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: '../foo/bar', directory: 'some/other#23/dir#42' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-git-url', () => { const expected = [new ExternalReference( @@ -225,7 +225,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'git@example.com:foo/bar', directory: 'some/other#23/dir#42' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('explicit-git-url', () => { const expected = [new ExternalReference( @@ -235,7 +235,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'git+https://example.com/dings.git', directory: 'some/other#23/dir#42' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('implicit-svn-url', () => { const expected = [new ExternalReference( @@ -245,7 +245,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'svn://example.com/foo/trunk', directory: 'some/other#23/dir#42' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('empty string', () => { const expected = [new ExternalReference( @@ -255,7 +255,7 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'http://example.com/foo', directory: '' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) test('undefined', () => { const expected = [new ExternalReference( @@ -265,11 +265,61 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () )] const data = { repository: { url: 'http://example.com/foo' } } const actual = sut.makeExternalReferences(data) - assert.deepEqual(actual, expected) + assert.deepStrictEqual(actual, expected) }) }) - + suite('from "dist"', () => { + test('with tarball', () => { + const expected = [new ExternalReference( + 'https://example.com/foo.tgz', + ExternalReferenceType.Distribution, + { comment: 'as detected from PackageJson property "dist.tarball"' } + )] + const data = { dist: { tarball: 'https://example.com/foo.tgz' } } + const actual = sut.makeExternalReferences(data) + assert.deepStrictEqual(actual, expected) + }) + test('with tarball and integrity', () => { + const expected = [new ExternalReference( + 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', + ExternalReferenceType.Distribution, + { + hashes: new HashDictionary([[HashAlgorithm['SHA-512'], 'b0572e8afb0367df5f6344dbbee442e820d707caffca569f8c900c9db485d32e0430cd7fd43b50a38d06d962b3d6b05bca2cf848b01cdd66bac99c82e1748639']]), + comment: 'as detected from PackageJson property "dist.tarball" and property "dist.integrity"' + } + )] + const data = { dist: { tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', integrity: 'sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ==' } } + const actual = sut.makeExternalReferences(data) + assert.deepStrictEqual(actual, expected) + }) + test('with tarball and shasum', () => { + const expected = [new ExternalReference( + 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', + ExternalReferenceType.Distribution, + { + hashes: new HashDictionary([[HashAlgorithm['SHA-1'], 'c305f0113d81d880f846d84f80c7f3237f197bab']]), + comment: 'as detected from PackageJson property "dist.tarball" and property "dist.shasum"' + } + )] + const data = { dist: { tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', shasum: 'c305f0113d81d880f846d84f80c7f3237f197bab' } } + const actual = sut.makeExternalReferences(data) + assert.deepStrictEqual(actual, expected) + }) + test('with tarball and integrity and shasum', () => { + const expected = [new ExternalReference( + 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', + ExternalReferenceType.Distribution, + { + hashes: new HashDictionary([[HashAlgorithm['SHA-1'], 'c305f0113d81d880f846d84f80c7f3237f197bab'], [HashAlgorithm['SHA-512'], 'b0572e8afb0367df5f6344dbbee442e820d707caffca569f8c900c9db485d32e0430cd7fd43b50a38d06d962b3d6b05bca2cf848b01cdd66bac99c82e1748639']]), + comment: 'as detected from PackageJson property "dist.tarball" and property "dist.integrity" and property "dist.shasum"' + } + )] + const data = { dist: { tarball: 'https://registry.npmjs.org/light-cycle/-/light-cycle-1.4.3.tgz', integrity: 'sha512-sFcuivsDZ99fY0TbvuRC6CDXB8r/ylafjJAMnbSF0y4EMM1/1DtQo40G2WKz1rBbyiz4SLAc3Wa6yZyC4XSGOQ==', shasum: 'c305f0113d81d880f846d84f80c7f3237f197bab' } } + const actual = sut.makeExternalReferences(data) + assert.deepStrictEqual(actual, expected) + }) + }) }) suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () => { diff --git a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js index dcacee92e..a91c4b1c1 100644 --- a/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js +++ b/tests/unit/Factories.FromNodePackageJson.PackageUrlFactory.js @@ -22,7 +22,7 @@ const assert = require('node:assert') const { suite, test } = require('mocha') const { - Factories: { FromNodePackageJson: { PackageUrlFactory }}, + Factories: { FromNodePackageJson: { PackageUrlFactory } }, Enums: { ComponentType, ExternalReferenceType }, Models: { Component, ExternalReference, ExternalReferenceRepository } } = require('../../') From 4e5ac614154e9267c98f2c3beb5b46a3db50d3c6 Mon Sep 17 00:00:00 2001 From: Jan Kowalleck Date: Wed, 4 Jun 2025 17:04:05 +0200 Subject: [PATCH 13/13] tests Signed-off-by: Jan Kowalleck --- ...ckageJson.ExternalReferenceFactory.test.js | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js index 9aaeec946..f57be4b42 100644 --- a/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js +++ b/tests/integration/Factories.FromNodePackageJson.ExternalReferenceFactory.test.js @@ -321,35 +321,3 @@ suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () }) }) }) - -suite('integration: Factories.FromNodePackageJson.ExternalReferenceFactory', () => { - const factory = new ExternalReferenceFactory() - - const cases = { - makeVcs: [ - // TODO - ], - makeHomepage: [ - // TODO - ], - makeIssueTracker: [ - // TODO - ], - makeDist: [ - // TODO - ], - } - cases.makeExternalReferences = [ - // TODO - ] - for (const [func, testData] of Object.entries(cases)) { - suite(func, () => { - for (const [label, data, expected] of testData) { - test(label, () => { - const actual = factory[func](data) - assert.deepStrictEqual(actual, expected) - }) - } - }) - } -})