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 lib/analyzer/package-managers/rpm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function purl(
qualifiers.repositories = repos.join(",");
}

if (pkg.epoch) {
if (pkg.epoch !== undefined && pkg.epoch !== null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this handle pkg.epoch = 0 correctly? I'm not too familiar with the dumpster fire wonderful joy that is true/false checks in JS, and I'm also not familiar with the pkg.epoch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disregard this, I need to learn to read the full PR message before looking at the code

qualifiers.epoch = String(pkg.epoch);
}

Expand Down
210 changes: 209 additions & 1 deletion test/lib/analyzer/package-managers/rpm.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,216 @@
import { PackageInfo } from "@snyk/rpm-parser/lib/rpm/types";
import * as fs from "fs";
import * as path from "path";
import { parseSourceRPM } from "../../../../lib/analyzer/package-managers/rpm";
import {
analyze,
mapRpmSqlitePackages,
parseSourceRPM,
} from "../../../../lib/analyzer/package-managers/rpm";
import { SourcePackage } from "../../../../lib/analyzer/types";

describe("RPM Package Version and Epoch Handling", () => {
describe("formats version without epoch", () => {
it("should format version string correctly", () => {
const mappedResults = mapRpmSqlitePackages(
"test-image",
[
{
name: "bash",
version: "5.1.16",
release: "4.el9",
size: 1024,
},
],
[],
);

expect(mappedResults.Analysis).toHaveLength(1);
expect(mappedResults.Analysis[0].Name).toBe("bash");
expect(mappedResults.Analysis[0].Version).toBe("5.1.16-4.el9");
expect(mappedResults.Analysis[0].Purl).toBe("pkg:rpm/[email protected]");
});
});

describe("formats version with epoch == 0", () => {
it("should include epoch=0 in PURL qualifier (bug fix)", () => {
const mappedResults = mapRpmSqlitePackages(
"test-image",
[
{
name: "libarchive",
version: "3.7.7",
release: "4.el10_0",
epoch: 0,
size: 2048,
},
],
[],
);

expect(mappedResults.Analysis).toHaveLength(1);
expect(mappedResults.Analysis[0].Name).toBe("libarchive");
expect(mappedResults.Analysis[0].Version).toBe("3.7.7-4.el10_0");
// Critical: epoch=0 must be explicitly included in PURL
expect(mappedResults.Analysis[0].Purl).toBe(
"pkg:rpm/[email protected]_0?epoch=0",
);
});

it("should include epoch=0 with distro qualifier", () => {
const mappedResults = mapRpmSqlitePackages(
"test-image",
[
{
name: "sqlite-libs",
version: "3.34.1",
release: "5.el8",
epoch: 0,
size: 1500,
},
],
[],
{ name: "rhel", version: "8.5" },
);

expect(mappedResults.Analysis[0].Purl).toBe(
"pkg:rpm/rhel/[email protected]?distro=rhel-8.5&epoch=0",
);
});
});

describe("formats version with non-zero epoch", () => {
it("should format version with epoch=1", () => {
const mappedResults = mapRpmSqlitePackages(
"test-image",
[
{
name: "findutils",
version: "4.5.11",
release: "6.amzn2",
epoch: 1,
size: 3000,
},
],
[],
);

expect(mappedResults.Analysis[0].Name).toBe("findutils");
expect(mappedResults.Analysis[0].Version).toBe("1:4.5.11-6.amzn2");
expect(mappedResults.Analysis[0].Purl).toBe(
"pkg:rpm/findutils@1:4.5.11-6.amzn2?epoch=1",
);
});
});

describe("epoch consistency across functions", () => {
it("should handle epoch=0 consistently in analyze() function", async () => {
const pkgs: PackageInfo[] = [
{
name: "openssl-libs",
version: "1.1.1",
release: "15.el8",
epoch: 0,
size: 6000,
},
];

const result = await analyze("test-image", pkgs, [], {
name: "rhel",
version: "8.2",
});

expect(result.Analysis[0].Purl).toBe(
"pkg:rpm/rhel/[email protected]?distro=rhel-8.2&epoch=0",
);
});
});

describe("multiple packages with different epochs", () => {
it("should correctly handle mixed epoch values", () => {
const packages: PackageInfo[] = [
{
name: "pkg-no-epoch",
version: "1.0.0",
release: "1",
size: 100,
},
{
name: "pkg-epoch-zero",
version: "2.0.0",
release: "1",
epoch: 0,
size: 200,
},
{
name: "pkg-epoch-one",
version: "3.0.0",
release: "1",
epoch: 1,
size: 300,
},
];

const result = mapRpmSqlitePackages("test-image", packages, []);

expect(result.Analysis[0].Purl).toBe("pkg:rpm/[email protected]");
expect(result.Analysis[1].Purl).toBe(
"pkg:rpm/[email protected]?epoch=0",
);
expect(result.Analysis[2].Purl).toBe(
"pkg:rpm/pkg-epoch-one@1:3.0.0-1?epoch=1",
);
});
});

describe("epoch with repositories and modules", () => {
it("should include epoch with other qualifiers", () => {
const result = mapRpmSqlitePackages(
"test-image",
[
{
name: "nodejs",
version: "10.21.0",
release: "3.module+el8.2.0",
epoch: 1,
module: "nodejs:10",
size: 7000,
},
],
["rhel-8-appstream"],
{ name: "rhel", version: "8.2" },
);

const purl = result.Analysis[0].Purl;
expect(purl).toContain("?");
expect(purl).toContain("epoch=1");
expect(purl).toContain("module=nodejs%3A10"); // : is URL-encoded as %3A
expect(purl).toContain("repositories=rhel-8-appstream");
expect(purl).toContain("distro=rhel-8.2");
});

it("should include epoch=0 with sourceRPM upstream qualifier", () => {
const result = mapRpmSqlitePackages(
"test-image",
[
{
name: "libxml2",
version: "2.9.7",
release: "14.el8",
epoch: 0,
sourceRPM: "libxml2-2.9.7-14.el8.src.rpm",
size: 8000,
},
],
[],
);

const purl = result.Analysis[0].Purl;
expect(purl).toContain("epoch=0");
expect(purl).toContain("upstream=libxml2%402.9.7"); // @ is URL-encoded as %40
});
});
});

describe("parseSourceRPM", () => {
it("should correctly parse all valid source RPM strings from source_rpms.csv", () => {
const csvFilePath = path.join(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2662,15 +2662,15 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-CGI",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "3.51-144.el6",
},
},
Object {
"id": "[email protected]",
"info": Object {
"name": "perl-ExtUtils-MakeMaker",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "6.55-144.el6",
},
},
Expand Down Expand Up @@ -2710,15 +2710,15 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-Test-Harness",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "3.17-144.el6",
},
},
Object {
"id": "[email protected]",
"info": Object {
"name": "perl-Test-Simple",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "0.92-144.el6",
},
},
Expand Down Expand Up @@ -5902,15 +5902,15 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-CGI",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "3.51-144.el6",
},
},
Object {
"id": "[email protected]",
"info": Object {
"name": "perl-ExtUtils-MakeMaker",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "6.55-144.el6",
},
},
Expand Down Expand Up @@ -5950,15 +5950,15 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-Test-Harness",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "3.17-144.el6",
},
},
Object {
"id": "[email protected]",
"info": Object {
"name": "perl-Test-Simple",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&upstream=perl%405.10.1",
"purl": "pkg:rpm/centos/[email protected]?distro=centos-6&epoch=0&upstream=perl%405.10.1",
"version": "0.92-144.el6",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5110,7 +5110,7 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-Errno",
"purl": "pkg:rpm/rhel/[email protected]?distro=rhel-8.2&repositories=rhel-8-for-x86_64-baseos-rpms%2Crhel-8-for-x86_64-appstream-rpms%2Crhel-8-for-x86_64-baseos-beta-rpms%2Crhel-8-for-x86_64-appstream-beta-rpms%2Crhel-8-for-x86_64-baseos-htb-rpms%2Crhel-8-for-x86_64-appstream-htb-rpms&upstream=perl%405.26.3",
"purl": "pkg:rpm/rhel/[email protected]?distro=rhel-8.2&epoch=0&repositories=rhel-8-for-x86_64-baseos-rpms%2Crhel-8-for-x86_64-appstream-rpms%2Crhel-8-for-x86_64-baseos-beta-rpms%2Crhel-8-for-x86_64-appstream-beta-rpms%2Crhel-8-for-x86_64-baseos-htb-rpms%2Crhel-8-for-x86_64-appstream-htb-rpms&upstream=perl%405.26.3",
"version": "1.28-416.el8",
},
},
Expand Down Expand Up @@ -5174,7 +5174,7 @@ Object {
"id": "[email protected]",
"info": Object {
"name": "perl-IO",
"purl": "pkg:rpm/rhel/[email protected]?distro=rhel-8.2&repositories=rhel-8-for-x86_64-baseos-rpms%2Crhel-8-for-x86_64-appstream-rpms%2Crhel-8-for-x86_64-baseos-beta-rpms%2Crhel-8-for-x86_64-appstream-beta-rpms%2Crhel-8-for-x86_64-baseos-htb-rpms%2Crhel-8-for-x86_64-appstream-htb-rpms&upstream=perl%405.26.3",
"purl": "pkg:rpm/rhel/[email protected]?distro=rhel-8.2&epoch=0&repositories=rhel-8-for-x86_64-baseos-rpms%2Crhel-8-for-x86_64-appstream-rpms%2Crhel-8-for-x86_64-baseos-beta-rpms%2Crhel-8-for-x86_64-appstream-beta-rpms%2Crhel-8-for-x86_64-baseos-htb-rpms%2Crhel-8-for-x86_64-appstream-htb-rpms&upstream=perl%405.26.3",
"version": "1.38-416.el8",
},
},
Expand Down