Skip to content
Open
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,4 @@ _generated_local/
task-lib/
tasks-common/

pnpm-exec-summary.json
pnpm-exec-summary.json
91 changes: 91 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.DownloadBehavior.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Tests how the download task behaves based on input combinations:
// - Universal (upack) packages route to the artifact tool instead of HTTP download
// - The extract flag is respected for npm/nuget but ignored for maven
// - View IDs are appended to feed identifiers when non-empty
import { TestHelpers } from './TestHelpers';
import { TestDataBuilder, TestData, TestEnvVars } from './TestConstants';

describe('DownloadPackageV1 L0 Suite - Download Behavior', function () {
this.timeout(30000);
beforeEach(() => TestHelpers.beforeEach());
afterEach(() => TestHelpers.afterEach());

describe('Universal Package (upack) Download', function () {
it('routes upack to downloadUniversalPackage with correct parameters', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forUniversalDownload()
);

// Verify the universal download was invoked with the correct feed and package info
TestHelpers.assertStdoutContains(tr, 'Universal package download called');
TestHelpers.assertStdoutContains(tr, 'feedId=feedId');
TestHelpers.assertStdoutContains(tr, `packageId=${TestData.defaultPackageGuid}`);
});

it('routes upack with project-scoped feed and splits projectId correctly', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forUniversalDownload({
[TestEnvVars.feed]: 'projectId/feedId'
})
);

TestHelpers.assertStdoutContains(tr, 'Universal package download called');
TestHelpers.assertStdoutContains(tr, 'feedId=feedId');
TestHelpers.assertStdoutContains(tr, 'projectId=projectId');
});
});

describe('Extract Flag Edge Cases', function () {
it('ignores extract flag for maven packages', async () => {
// Maven uses multi-file download, extract is not applicable
const tr = await TestHelpers.runTest(
TestDataBuilder.forMavenDownload({
[TestEnvVars.extract]: 'true'
})
);

TestHelpers.assertSuccess(tr);
// Maven files should be downloaded directly, not extracted
TestHelpers.assertFileInDestination('packageName.jar');
TestHelpers.assertFileInDestination('packageName.pom');
});

it('does not extract npm when extract is false', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNpmDownload({
[TestEnvVars.extract]: 'false'
})
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 0);
TestHelpers.assertFileInDestination('singlePackageName.tgz');
});
});

describe('View Handling', function () {
it('downloads successfully when a view is specified', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetDownload({
[TestEnvVars.feed]: 'feedId',
[TestEnvVars.view]: 'releaseView'
})
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
});

it('downloads successfully when view is whitespace-only (treated as empty)', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetDownload({
[TestEnvVars.feed]: 'feedId',
[TestEnvVars.view]: ' '
})
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
});
});
});
79 changes: 79 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.InputValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as assert from 'assert';
import { TestHelpers } from './TestHelpers';
import { TestDataBuilder, TestEnvVars } from './TestConstants';

describe('DownloadPackageV1 L0 Suite - Input Validation & Edge Cases', function () {
this.timeout(30000);
beforeEach(() => TestHelpers.beforeEach());
afterEach(() => TestHelpers.afterEach());

describe('Skip Download', function () {
it('skips download when Packaging.SkipDownload is true', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forSkipDownload()
);

// Task returns early without failing — no files downloaded
assert(tr.stderr.length === 0, 'Should not have written to stderr');
TestHelpers.assertStdoutContains(tr, 'Download Package skipped.');
TestHelpers.assertFileCount(TestHelpers.tempDir, 0);
TestHelpers.assertFileCount(TestHelpers.destinationDir, 0);
});
});

describe('Feed Scoping', function () {
it('downloads successfully with org-scoped feed', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetDownload({
[TestEnvVars.feed]: 'feedId'
})
);

TestHelpers.assertSuccess(tr);
// Verify the download actually completed by checking file output
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
TestHelpers.assertStdoutContains(tr, '"FeedId":"feedId@viewId"');
TestHelpers.assertStdoutDoesNotContain(tr, '"Project":"projectId"');
});

it('downloads successfully with project-scoped feed', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forProjectScopedFeed()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
TestHelpers.assertStdoutContains(tr, '"FeedId":"feedId@viewId"');
TestHelpers.assertStdoutContains(tr, '"Project":"projectId"');
});
});

describe('Package Name Resolution', function () {
it('resolves package name to ID and downloads successfully', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNameResolution()
);

TestHelpers.assertSuccess(tr);
// Prove the resolution worked: file was downloaded using the resolved ID
TestHelpers.assertStdoutContains(tr, 'Trying to resolve package name packageName to id.');
TestHelpers.assertStdoutContains(tr, 'Resolved package id: 6f598cbe-a5e2-4f75-aa78-e0fd08301a15');
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
});

it('skips name resolution when definition is already a GUID', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetDownload()
);

TestHelpers.assertSuccess(tr);
// File still downloads — GUID was used directly
TestHelpers.assertStdoutDoesNotContain(tr, 'Trying to resolve package name');
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
});
});
});
21 changes: 21 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.MavenDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { TestHelpers } from './TestHelpers';
import { TestDataBuilder } from './TestConstants';

describe('DownloadPackageV1 L0 Suite - Maven Downloads', function () {
this.timeout(30000);
beforeEach(() => TestHelpers.beforeEach());
afterEach(() => TestHelpers.afterEach());

describe('Maven Multi-File Download', function () {
it('downloads jar and pom files without extracting them', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forMavenDownload()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.destinationDir, 2);
TestHelpers.assertFileInDestination('packageName.jar');
TestHelpers.assertFileInDestination('packageName.pom');
});
});
});
36 changes: 36 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.NpmDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { TestHelpers } from './TestHelpers';
import { TestDataBuilder } from './TestConstants';

describe('DownloadPackageV1 L0 Suite - Npm Downloads', function () {
this.timeout(30000);
beforeEach(() => TestHelpers.beforeEach());
afterEach(() => TestHelpers.afterEach());

describe('Npm Download with Extraction', function () {
it('downloads npm file as tgz and extracts it', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNpmDownload()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 1);
TestHelpers.assertFileDownloaded('singlePackageName.tgz');
TestHelpers.assertFileExtracted('npmFile');
});
});

describe('Npm Download Failures', function () {
it('fails with download error message when npm download fails', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forDownloadError({
'__packageType__': 'npm',
'__feed__': 'feedId'
})
);

TestHelpers.assertFailure(tr);
// Verify no files were left behind
TestHelpers.assertFileCount(TestHelpers.tempDir, 0);
});
});
});
69 changes: 69 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.NuGetDownload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { TestHelpers } from './TestHelpers';
import { TestDataBuilder } from './TestConstants';

describe('DownloadPackageV1 L0 Suite - NuGet Downloads', function () {
this.timeout(30000);
beforeEach(() => TestHelpers.beforeEach());
afterEach(() => TestHelpers.afterEach());

describe('NuGet Download with Extraction', function () {
it('downloads nuget file as nupkg and extracts it', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetDownload()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 1);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
});

it('downloads nuget from project-scoped feed and extracts it', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forProjectScopedFeed()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 1);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
});

it('resolves package name to ID, then downloads and extracts', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNameResolution()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 1);
TestHelpers.assertFileDownloaded('singlePackageName.nupkg');
TestHelpers.assertFileExtracted('nugetFile');
});
});

describe('NuGet Download without Extraction', function () {
it('downloads nuget file as nupkg without extracting', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forNuGetNoExtract()
);

TestHelpers.assertSuccess(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 0);
TestHelpers.assertFileCount(TestHelpers.destinationDir, 1);
TestHelpers.assertFileInDestination('singlePackageName.nupkg');
});
});

describe('NuGet Extraction Failures', function () {
it('fails when nupkg contains a bad zip', async () => {
const tr = await TestHelpers.runTest(
TestDataBuilder.forBadZip()
);

TestHelpers.assertFailure(tr);
TestHelpers.assertFileCount(TestHelpers.tempDir, 1);
TestHelpers.assertFileDownloaded('badNupkgPackageName.nupkg');
TestHelpers.assertFileNotExists(TestHelpers.destinationDir, 'nugetFile');
});
});
});
85 changes: 85 additions & 0 deletions Tasks/DownloadPackageV1/Tests/L0.PackageBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as assert from 'assert';
import { PackageUrlsBuilder } from '../packagebuilder';
import { SingleFilePackage } from '../singlefilepackage';
import { MultiFilePackage } from '../multifilepackage';

describe('DownloadPackageV1 L0 Suite - PackageBuilder Unit Behavior', function () {
it('maps nuget to single-file package with .nupkg extension', async () => {
const builder = new PackageUrlsBuilder().ofType('nuget');
const pkg = await builder.build();

assert.strictEqual(builder.PackageProtocolAreaName, 'NuGet');
assert.strictEqual(builder.Extension, '.nupkg');
assert(pkg instanceof SingleFilePackage);
});

it('maps npm to single-file package with .tgz extension', async () => {
const builder = new PackageUrlsBuilder().ofType('npm');
const pkg = await builder.build();

assert.strictEqual(builder.PackageProtocolAreaName, 'npm');
assert.strictEqual(builder.Extension, '.tgz');
assert(pkg instanceof SingleFilePackage);
});

it('maps maven and pypi to multi-file package', async () => {
const mavenBuilder = new PackageUrlsBuilder().ofType('maven');
const pypiBuilder = new PackageUrlsBuilder().ofType('pypi');

const mavenPackage = await mavenBuilder.build();
const pypiPackage = await pypiBuilder.build();

assert(mavenPackage instanceof MultiFilePackage);
assert(pypiPackage instanceof MultiFilePackage);
});

it('builds maven route params with artifact path and fallback groupId', () => {
const builder = new PackageUrlsBuilder().ofType('maven');
const routeParams = builder.GetRouteParams(
'feedId',
'projectId',
{
protocolMetadata: {
data: {
groupId: undefined,
parent: { groupId: 'com.example' },
artifactId: 'demo-artifact',
version: '1.2.3'
}
}
},
{ name: 'demo-artifact-1.2.3.pom' }
);

assert.strictEqual(routeParams.feed, 'feedId');
assert.strictEqual(routeParams.project, 'projectId');
assert.strictEqual(routeParams.path, 'com.example/demo-artifact/1.2.3/demo-artifact-1.2.3.pom');
});

it('builds python route params from protocol metadata', () => {
const builder = new PackageUrlsBuilder().ofType('pypi');
const routeParams = builder.GetRouteParams(
'feedId',
'projectId',
{
protocolMetadata: {
data: {
name: 'wheelpkg',
version: '0.5.0'
}
}
},
{ name: 'wheelpkg-0.5.0-py3-none-any.whl' }
);

assert.strictEqual(routeParams.feedId, 'feedId');
assert.strictEqual(routeParams.project, 'projectId');
assert.strictEqual(routeParams.packageName, 'wheelpkg');
assert.strictEqual(routeParams.packageVersion, '0.5.0');
assert.strictEqual(routeParams.fileName, 'wheelpkg-0.5.0-py3-none-any.whl');
});

it('throws for unsupported package type', () => {
assert.throws(() => new PackageUrlsBuilder().ofType('invalid-type'));
});
});
Loading
Loading