Skip to content

Commit 8f62926

Browse files
committed
test: extract purl-spec test suite to separate file
Move official purl-spec test-suite-data tests (456 tests) from package-url.test.mts to purl-spec.test.mts for better organization. These tests validate compliance with the Package URL specification using official test data. Reduces main test file from 3618 to 3460 lines (-158 lines).
1 parent 04b7c40 commit 8f62926

File tree

2 files changed

+202
-179
lines changed

2 files changed

+202
-179
lines changed

test/package-url.test.mts

Lines changed: 0 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2020
SOFTWARE.
2121
*/
2222

23-
import path from 'node:path'
24-
25-
import { glob } from 'fast-glob'
2623
import { describe, expect, it } from 'vitest'
2724

28-
import { readJson } from '@socketsecurity/registry/lib/fs'
29-
import {
30-
isObject,
31-
toSortedObjectFromEntries,
32-
} from '@socketsecurity/registry/lib/objects'
33-
3425
import {
3526
testInvalidParam,
3627
testInvalidStringParam,
@@ -90,17 +81,6 @@ function getNpmId(purl: any) {
9081
return `${namespace?.length > 0 ? `${namespace}/` : ''}${name}`
9182
}
9283

93-
function toUrlSearchParams(search: any) {
94-
const searchParams = new URLSearchParams()
95-
const entries = search.split('&')
96-
for (let i = 0, { length } = entries; i < length; i += 1) {
97-
const pairs = entries[i].split('=')
98-
const value = decodeURIComponent(pairs.at(1) ?? '')
99-
searchParams.append(pairs[0], value)
100-
}
101-
return searchParams
102-
}
103-
10484
describe('PackageURL', () => {
10585
describe('KnownQualifierNames', () => {
10686
it.each([
@@ -538,165 +518,6 @@ describe('PackageURL', () => {
538518
})
539519
})
540520

541-
describe('test-suite-data', async () => {
542-
// Tests from the official purl-spec test suite (data/*.json files)
543-
const TEST_FILES = (
544-
await Promise.all(
545-
(
546-
await glob(['**/**.json'], {
547-
absolute: true,
548-
cwd: path.join(__dirname, 'data'),
549-
})
550-
).map(p => readJson(p)),
551-
)
552-
)
553-
.filter(Boolean)
554-
.flatMap((o: any) => o.tests ?? [])
555-
556-
for (const obj of TEST_FILES) {
557-
const { expected_failure, expected_output, test_type } = obj
558-
559-
const inputObj = isObject(obj.input) ? obj.input : undefined
560-
561-
const inputStr = typeof obj.input === 'string' ? obj.input : undefined
562-
563-
if (!inputObj && !inputStr) {
564-
continue
565-
}
566-
567-
const expectedObj = isObject(expected_output)
568-
? expected_output
569-
: undefined
570-
571-
const expectedStr =
572-
typeof expected_output === 'string' ? expected_output : undefined
573-
574-
if (!expectedObj && !expectedStr) {
575-
continue
576-
}
577-
578-
describe(obj.description, () => {
579-
if (expected_failure) {
580-
if (test_type === 'parse' && inputStr) {
581-
// Tests expected parse failures from test suite
582-
it(`should not be possible to parse invalid ${expectedObj?.type ?? 'type'} PackageURLs`, () => {
583-
expect(() => PackageURL.fromString(inputStr)).toThrow(
584-
/missing the required|Invalid purl/,
585-
)
586-
})
587-
}
588-
if (test_type === 'build' && inputObj) {
589-
// Tests expected constructor failures from test suite
590-
it(`should not be possible to create invalid ${inputObj.type ?? 'type'} PackageURLs`, () => {
591-
expect(
592-
() =>
593-
new PackageURL(
594-
inputObj.type,
595-
inputObj.namespace,
596-
inputObj.name,
597-
inputObj.version,
598-
inputObj.qualifiers,
599-
inputObj.subpath,
600-
),
601-
).toThrow(/is a required|Invalid purl/)
602-
})
603-
}
604-
} else if (test_type === 'parse' && inputStr && expectedObj) {
605-
// Tests successful parsing from test suite
606-
it(`should be able to parse valid ${expectedObj.type ?? 'type'} PackageURLs`, () => {
607-
const purl = PackageURL.fromString(inputStr)
608-
expect(purl.type).toBe(expectedObj.type)
609-
expect(purl.name).toBe(expectedObj.name)
610-
expect(purl.namespace).toBe(expectedObj.namespace ?? undefined)
611-
expect(purl.version).toBe(expectedObj.version ?? undefined)
612-
expect(purl.qualifiers).toStrictEqual(
613-
expectedObj.qualifiers
614-
? { __proto__: null, ...expectedObj.qualifiers }
615-
: undefined,
616-
)
617-
expect(purl.subpath).toBe(expectedObj.subpath ?? undefined)
618-
})
619-
} else if (test_type === 'build' && inputObj && expectedStr) {
620-
// Tests toString() output from test suite
621-
it(`should be able to convert valid ${inputObj.type ?? 'type'} PackageURLs to a string`, () => {
622-
const purl = new PackageURL(
623-
inputObj.type,
624-
inputObj.namespace,
625-
inputObj.name,
626-
inputObj.version,
627-
inputObj.qualifiers,
628-
inputObj.subpath,
629-
)
630-
const purlToStr = purl.toString()
631-
if (purl.qualifiers) {
632-
const markIndex = expectedStr.indexOf('?')
633-
const beforeMarkToStr = purlToStr.slice(0, markIndex)
634-
const beforeExpectedStr = expectedStr.slice(0, markIndex)
635-
expect(beforeMarkToStr).toBe(beforeExpectedStr)
636-
637-
const afterMarkToStr = purlToStr.slice(markIndex + 1)
638-
const afterExpectedStr = expectedStr.slice(markIndex + 1)
639-
const actualParams = toSortedObjectFromEntries(
640-
toUrlSearchParams(afterMarkToStr).entries(),
641-
)
642-
const expectedParams = toSortedObjectFromEntries(
643-
toUrlSearchParams(afterExpectedStr).entries(),
644-
)
645-
expect(actualParams).toStrictEqual(expectedParams)
646-
} else {
647-
expect(purlToStr).toBe(expectedStr)
648-
}
649-
})
650-
} else if (test_type === 'roundtrip' && inputStr && expectedStr) {
651-
it(`should roundtrip ${expectedStr.split('/')[1]?.split('@')[0] ?? 'purl'}`, () => {
652-
const purl = PackageURL.fromString(inputStr)
653-
const purlToStr = purl.toString()
654-
655-
// Special case: The test suite has a known issue where it expects
656-
// unencoded + in subpaths for roundtrip, but that's not correct.
657-
// We normalize to the canonical form with %2B per URL encoding rules.
658-
let normalizedExpected = expectedStr
659-
if (
660-
expectedStr.includes('#') &&
661-
expectedStr.includes('+') &&
662-
inputStr === 'pkg:cocoapods/[email protected]#NSData+zlib'
663-
) {
664-
normalizedExpected = expectedStr.replace(
665-
'#NSData+zlib',
666-
'#NSData%2Bzlib',
667-
)
668-
}
669-
670-
if (purl.qualifiers) {
671-
const markIndex = normalizedExpected.indexOf('?')
672-
const beforeMarkToStr = purlToStr.slice(0, markIndex)
673-
const beforeExpectedStr = normalizedExpected.slice(0, markIndex)
674-
expect(beforeMarkToStr).toBe(beforeExpectedStr)
675-
676-
const afterMarkToStr = purlToStr.slice(markIndex + 1)
677-
const afterExpectedStr = normalizedExpected.slice(markIndex + 1)
678-
const actualParams = toSortedObjectFromEntries(
679-
toUrlSearchParams(afterMarkToStr).entries(),
680-
)
681-
const expectedParams = toSortedObjectFromEntries(
682-
toUrlSearchParams(afterExpectedStr).entries(),
683-
)
684-
expect(actualParams).toStrictEqual(expectedParams)
685-
} else {
686-
expect(purlToStr).toBe(normalizedExpected)
687-
}
688-
})
689-
} else {
690-
it(`should handle test case: ${test_type}`, () => {
691-
throw new Error(
692-
`Unhandled test case: test_type=${test_type}, has inputStr=${!!inputStr}, has inputObj=${!!inputObj}, has expectedStr=${!!expectedStr}, has expectedObj=${!!expectedObj}, expected_failure=${expected_failure}`,
693-
)
694-
})
695-
}
696-
})
697-
}
698-
})
699-
700521
describe('npm', () => {
701522
it("should allow legacy names to be mixed case, match a builtin, or contain ~'!()* characters", () => {
702523
// Tests npm legacy package exceptions (historical packages with special names)

0 commit comments

Comments
 (0)