Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/api/nes/nes.client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as apollo from '@apollo/client/core/index.js';
import { PackageURL } from 'packageurl-js';

import { ApolloClient } from '../../api/client.ts';
import type {
Expand Down Expand Up @@ -59,7 +60,8 @@ export const batchSubmitPurls = async (
batchSize = DEFAULT_SCAN_BATCH_SIZE,
): Promise<ScanResult> => {
try {
const batches = createBatches(purls, batchSize);
const dedupedAndEncodedPurls = dedupeAndEncodePurls(purls);
const batches = createBatches(dedupedAndEncodedPurls, batchSize);
debugLogger('Processing %d batches', batches.length);

if (batches.length === 0) {
Expand All @@ -80,6 +82,24 @@ export const batchSubmitPurls = async (
}
};

export const dedupeAndEncodePurls = (purls: string[]): string[] => {
const dedupedAndEncodedPurls = new Set<string>();

for (const purl of purls) {
try {
// The PackageURL.fromString method encodes each part of the purl
const encodedPurl = PackageURL.fromString(purl).toString();
if (!dedupedAndEncodedPurls.has(encodedPurl)) {
Comment thread
rlmestre marked this conversation as resolved.
dedupedAndEncodedPurls.add(encodedPurl);
}
} catch (error) {
debugLogger('Error encoding purl: %s', error);
}
}

return Array.from(dedupedAndEncodedPurls);
};

export const createBatches = (items: string[], batchSize: number): string[][] => {
const numberOfBatches = Math.ceil(items.length / batchSize);

Expand Down
80 changes: 56 additions & 24 deletions test/api/nes.client.test.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,67 @@
import assert from 'node:assert';
import { describe, it } from 'node:test';
import { createBatches } from '../../src/api/nes/nes.client.ts';
import { createBatches, dedupeAndEncodePurls } from '../../src/api/nes/nes.client.ts';
import { DEFAULT_SCAN_BATCH_SIZE } from '../../src/api/types/hd-cli.types.ts';

describe('createBatches', () => {
it('should handle empty array', () => {
const result = createBatches([], DEFAULT_SCAN_BATCH_SIZE);
assert.deepStrictEqual(result, []);
});
describe('nes.client', () => {
describe('createBatches', () => {
it('should handle empty array', () => {
const result = createBatches([], DEFAULT_SCAN_BATCH_SIZE);
assert.deepStrictEqual(result, []);
});

it('should create single batch when items length is less than batch size', () => {
const items = ['a', 'b', 'c'];
const result = createBatches(items, 5);
assert.deepStrictEqual(result, [['a', 'b', 'c']]);
});
it('should create single batch when items length is less than batch size', () => {
const items = ['a', 'b', 'c'];
const result = createBatches(items, 5);
assert.deepStrictEqual(result, [['a', 'b', 'c']]);
});

it('should create single batch when items length equals batch size', () => {
const items = ['a', 'b', 'c', 'd', 'e'];
const result = createBatches(items, 5);
assert.deepStrictEqual(result, [['a', 'b', 'c', 'd', 'e']]);
});
it('should create single batch when items length equals batch size', () => {
const items = ['a', 'b', 'c', 'd', 'e'];
const result = createBatches(items, 5);
assert.deepStrictEqual(result, [['a', 'b', 'c', 'd', 'e']]);
});

it('should create multiple batches when items length exceeds batch size', () => {
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
const result = createBatches(items, 3);
assert.deepStrictEqual(result, [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'], ['j']]);
});

it('should create multiple batches when items length exceeds batch size', () => {
const items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
const result = createBatches(items, 3);
assert.deepStrictEqual(result, [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'], ['j']]);
it('should handle batch size of 1', () => {
const items = ['a', 'b', 'c'];
const result = createBatches(items, 1);
assert.deepStrictEqual(result, [['a'], ['b'], ['c']]);
});
});

it('should handle batch size of 1', () => {
const items = ['a', 'b', 'c'];
const result = createBatches(items, 1);
assert.deepStrictEqual(result, [['a'], ['b'], ['c']]);
describe('dedupeAndEncodePurls', () => {
const inputs = [
{
purls: ['pkg:npm/@angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/%40angular/core@14.3.0'],
expected: ['pkg:npm/%40angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1'],
description: 'should dedupe angular core purls',
},
{
purls: ['pkg:npm/@angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/rxjs@6.6.7'],
expected: ['pkg:npm/%40angular/core@14.3.0', 'pkg:npm/npm-bundled@2.0.1', 'pkg:npm/rxjs@6.6.7'],
description: 'should not dedupe unique purls',
},
{
purls: [
'pkg:maven/org.apache.commons/commons-lang3@3.12.0',
'pkg:maven/org.apache.commons/commons-lang3@3.12.0',
'pkg:maven/org.apache.commons/commons-lang3@3.12.0',
],
expected: ['pkg:maven/org.apache.commons/commons-lang3@3.12.0'],
description: 'should dedupe maven purls',
},
];
for (const input of inputs) {
it(`should dedupe and encode purls: ${input.description}`, () => {
const result = dedupeAndEncodePurls(input.purls);
assert.deepStrictEqual(result, input.expected);
});
}
});
});