diff --git a/src/rebar3_sbom_cpe.erl b/src/rebar3_sbom_cpe.erl index 92aaaea..e5779c8 100644 --- a/src/rebar3_sbom_cpe.erl +++ b/src/rebar3_sbom_cpe.erl @@ -1,6 +1,6 @@ -module(rebar3_sbom_cpe). --export([hex/3]). +-export([cpe/3]). % Includes -include("rebar3_sbom.hrl"). @@ -25,60 +25,62 @@ %--- API ----------------------------------------------------------------------- --spec hex(Name, Version, Url) -> CPE when +-spec cpe(Name, Version, Url) -> CPE when Name :: bitstring(), Version :: bitstring(), Url :: bitstring() | undefined, CPE :: bitstring(). -hex(Name, undefined, Url) -> - hex(Name, <<"*">>, Url); -hex(<<"hex_core">>, Version, _) -> +cpe(Name, undefined, Url) -> + cpe(Name, <<"*">>, Url); +cpe(<<"hex_core">>, Version, _) -> <>; -hex(<<"plug">>, Version, _) -> +cpe(<<"plug">>, Version, _) -> <>; -hex(<<"phoenix">>, Version, _) -> +cpe(<<"phoenix">>, Version, _) -> <>; -hex(<<"coherence">>, Version, _) -> +cpe(<<"coherence">>, Version, _) -> <>; -hex(<<"xain">>, Version, _) -> +cpe(<<"xain">>, Version, _) -> <>; -hex(<<"sweet_xml">>, Version, _) -> +cpe(<<"sweet_xml">>, Version, _) -> <>; -hex(<<"erlang/otp">>, Version, _) -> +cpe(<<"erlang/otp">>, Version, _) -> <>; -hex(<<"rebar3">>, Version, _) -> +cpe(<<"rebar3">>, Version, _) -> <>; -hex(<<"elixir">>, Version, _) -> +cpe(<<"elixir">>, Version, _) -> <>; -hex(_Name, _Version, undefined) -> +cpe(_Name, _Version, undefined) -> undefined; -hex(Name, Version, Url) -> +cpe(Name, Version, Url) -> Organization = github_url(Url), - cpe(Organization, Name, Version). + build_cpe(Organization, Name, Version). %--- Private ------------------------------------------------------------------- -spec github_url(Url) -> Organization when Url :: bitstring(), Organization :: bitstring(). -github_url(Url) -> - <<"https://github.com/", Rest/bitstring>> = Url, +github_url(<<"https://github.com/", Rest/bitstring>>) -> + [Organization | _] = string:split(Rest, "/"), + Organization; +github_url(<<"git@github.com:", Rest/bitstring>>) -> [Organization | _] = string:split(Rest, "/"), Organization. --spec cpe(Organization, Name, Version) -> CPE when +-spec build_cpe(Organization, Name, Version) -> CPE when Organization :: bitstring(), Name :: bitstring(), Version :: bitstring(), CPE :: bitstring(). -cpe(Organization, Name, Version) -> +build_cpe(Organization, Name, Version) -> <>. diff --git a/src/rebar3_sbom_prv.erl b/src/rebar3_sbom_prv.erl index 43d9b03..9fb8191 100644 --- a/src/rebar3_sbom_prv.erl +++ b/src/rebar3_sbom_prv.erl @@ -95,7 +95,6 @@ dep_info(Dep) -> Licenses = lists:usort(Licenses0 ++ HexMetadataLicenses), Links = proplists:get_value(links, Details, []), GitHubLink = get_github_link(HexMetadata, Links), - CPE = rebar3_sbom_cpe:hex(Name, list_to_binary(Version), GitHubLink), Common = [ {authors, proplists:get_value(maintainers, Details, [])}, @@ -104,7 +103,7 @@ dep_info(Dep) -> {external_references, ExternalReferences}, {dependencies, Deps}, {scope, required}, - {cpe, CPE} + {github_link, GitHubLink} ], dep_info(Name, Version, Source, Common). @@ -177,45 +176,64 @@ valid_external_reference_types() -> "patent", "patent-family", "patent-assertion", "citation", "other"]. dep_info(_Name, _Version, {pkg, Name, Version, Sha256}, Common) -> + GitHubLink = proplists:get_value(github_link, Common, undefined), [ {name, Name}, {version, Version}, {purl, rebar3_sbom_purl:hex(Name, Version)}, - {sha256, string:lowercase(Sha256)} + {sha256, string:lowercase(Sha256)}, + {cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)} | Common ]; dep_info(_Name, _Version, {pkg, Name, Version, _InnerChecksum, OuterChecksum, _RepoConfig}, Common) -> + GitHubLink = proplists:get_value(github_link, Common, undefined), [ {name, Name}, {version, Version}, {purl, rebar3_sbom_purl:hex(Name, Version)}, - {sha256, string:lowercase(OuterChecksum)} + {sha256, string:lowercase(OuterChecksum)}, + {cpe, rebar3_sbom_cpe:cpe(Name, Version, GitHubLink)} | Common ]; dep_info(Name, DepVersion, {git, Git, GitRef}, Common) -> - {Version, Purl} = + {Version, Purl, CPE} = case GitRef of {tag, Tag} -> - {Tag, rebar3_sbom_purl:git(Name, Git, Tag)}; + GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Tag), list_to_binary(Git)), + {Tag, rebar3_sbom_purl:git(Name, Git, Tag), GeneratedCPE}; {branch, Branch} -> - {DepVersion, rebar3_sbom_purl:git(Name, Git, Branch)}; + GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Branch), list_to_binary(Git)), + {DepVersion, rebar3_sbom_purl:git(Name, Git, Branch), GeneratedCPE}; {ref, Ref} -> - {DepVersion, rebar3_sbom_purl:git(Name, Git, Ref)} + GeneratedCPE = rebar3_sbom_cpe:cpe(Name, list_to_binary(Ref), list_to_binary(Git)), + {DepVersion, rebar3_sbom_purl:git(Name, Git, Ref), GeneratedCPE} end, [ {name, Name}, {version, Version}, - {purl, Purl} + {purl, Purl}, + {cpe, CPE} | maybe_update_licenses(Purl, Common) ]; dep_info(Name, Version, {git_subdir, Git, Ref, _Dir}, Common) -> dep_info(Name, Version, {git, Git, Ref}, Common); +dep_info(Name, Version, checkout, Common) -> + GitHubLink = proplists:get_value(github_link, Common, undefined), + [ + {name, Name}, + {version, Version}, + {purl, rebar3_sbom_purl:local_otp_app(Name, Version)}, + {cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)} + | Common ]; + dep_info(Name, Version, root_app, Common) -> + GitHubLink = proplists:get_value(github_link, Common, undefined), Purl = rebar3_sbom_purl:hex(Name, Version), [ {name, Name}, {version, Version}, - {purl, Purl} + {purl, Purl}, + {cpe, rebar3_sbom_cpe:cpe(Name, list_to_binary(Version), GitHubLink)} | Common ]. filepath(?DEFAULT_OUTPUT, Format) -> diff --git a/src/rebar3_sbom_purl.erl b/src/rebar3_sbom_purl.erl index 7fee33e..343fd4d 100644 --- a/src/rebar3_sbom_purl.erl +++ b/src/rebar3_sbom_purl.erl @@ -2,7 +2,7 @@ % https://github.com/package-url/purl-spec --export([hex/2, git/3, github/2, bitbucket/2]). +-export([hex/2, git/3, github/2, bitbucket/2, local_otp_app/2, local/2]). hex(Name, Version) -> purl(["hex", string:lowercase(Name)], Version). @@ -43,6 +43,12 @@ bitbucket(Repo, Ref) -> [Organization, Name | _] = string:split(Repo, "/"), purl(["bitbucket", string:lowercase(Organization), string:lowercase(Name)], Ref). +local_otp_app(Name, Version) -> + purl(["otp", string:lowercase(Name)], Version). + +local(Name, Version) -> + purl(["generic", string:lowercase(Name)], Version). + purl(PathSegments, Version) -> Path = lists:join("/", [escape(Segment) || Segment <- PathSegments]), unicode:characters_to_binary(io_lib:format("pkg:~s@~s", [Path, escape(Version)])). diff --git a/test/local_app/_checkouts/checkout_app/rebar.config b/test/local_app/_checkouts/checkout_app/rebar.config new file mode 100644 index 0000000..69cf984 --- /dev/null +++ b/test/local_app/_checkouts/checkout_app/rebar.config @@ -0,0 +1,7 @@ +{erl_opts, [debug_info]}. +{deps, []}. + +{shell, [ + %% {config, "config/sys.config"}, + {apps, [checkout_app]} +]}. diff --git a/test/local_app/_checkouts/checkout_app/src/checkout_app.app.src b/test/local_app/_checkouts/checkout_app/src/checkout_app.app.src new file mode 100644 index 0000000..c610c35 --- /dev/null +++ b/test/local_app/_checkouts/checkout_app/src/checkout_app.app.src @@ -0,0 +1,14 @@ +{application, checkout_app, [ + {description, "An OTP application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {checkout_app_app, []}}, + {applications, [ + kernel, + stdlib + ]}, + {env, []}, + {modules, []}, + {licenses, ["Apache-2.0"]}, + {links, []} + ]}. diff --git a/test/local_app/_checkouts/checkout_app/src/checkout_app_app.erl b/test/local_app/_checkouts/checkout_app/src/checkout_app_app.erl new file mode 100644 index 0000000..c5bf12e --- /dev/null +++ b/test/local_app/_checkouts/checkout_app/src/checkout_app_app.erl @@ -0,0 +1,18 @@ +%%%------------------------------------------------------------------- +%% @doc checkout_app public API +%% @end +%%%------------------------------------------------------------------- + +-module(checkout_app_app). + +-behaviour(application). + +-export([start/2, stop/1]). + +start(_StartType, _StartArgs) -> + checkout_app_sup:start_link(). + +stop(_State) -> + ok. + +%% internal functions diff --git a/test/local_app/_checkouts/checkout_app/src/checkout_app_sup.erl b/test/local_app/_checkouts/checkout_app/src/checkout_app_sup.erl new file mode 100644 index 0000000..95d13a7 --- /dev/null +++ b/test/local_app/_checkouts/checkout_app/src/checkout_app_sup.erl @@ -0,0 +1,37 @@ +%%%------------------------------------------------------------------- +%% @doc checkout_app top level supervisor. +%% @end +%%%------------------------------------------------------------------- + +-module(checkout_app_sup). + +-behaviour(supervisor). + +-export([start_link/0]). + +-export([init/1]). + +-define(SERVER, ?MODULE). + +start_link() -> + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + +%% sup_flags() = #{strategy => strategy(), % optional +%% intensity => non_neg_integer(), % optional +%% period => pos_integer()} % optional +%% child_spec() = #{id => child_id(), % mandatory +%% start => mfargs(), % mandatory +%% restart => restart(), % optional +%% shutdown => shutdown(), % optional +%% type => worker(), % optional +%% modules => modules()} % optional +init([]) -> + SupFlags = #{ + strategy => one_for_all, + intensity => 0, + period => 1 + }, + ChildSpecs = [], + {ok, {SupFlags, ChildSpecs}}. + +%% internal functions diff --git a/test/local_app/rebar.config b/test/local_app/rebar.config index e98dcdd..fe9cb63 100644 --- a/test/local_app/rebar.config +++ b/test/local_app/rebar.config @@ -3,7 +3,9 @@ {rebar3_sbom, [ ]}. -{deps, []}. +{deps, [ + checkout_app +]}. {shell, [ %% {config, "config/sys.config"}, diff --git a/test/rebar3_sbom_cpe_SUITE.erl b/test/rebar3_sbom_cpe_SUITE.erl index 688c817..3caf765 100644 --- a/test/rebar3_sbom_cpe_SUITE.erl +++ b/test/rebar3_sbom_cpe_SUITE.erl @@ -51,65 +51,65 @@ end_per_testcase(_, _Config) -> %--- Test cases ---------------------------------------------------------------- hex_core_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"hex_core">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"hex_core">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:hex:hex_core:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"hex_core">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"hex_core">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:hex:hex_core:*:*:*:*:*:*:*:*">>, CPENoVersion). plug_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"plug">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"plug">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:elixir-plug:plug:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"plug">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"plug">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:elixir-plug:plug:*:*:*:*:*:*:*:*">>, CPENoVersion). phoenix_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"phoenix">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"phoenix">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:phoenixframework:phoenix:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"phoenix">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"phoenix">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:phoenixframework:phoenix:*:*:*:*:*:*:*:*">>, CPENoVersion). coherence_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"coherence">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"coherence">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:coherence_project:coherence:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"coherence">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"coherence">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:coherence_project:coherence:*:*:*:*:*:*:*:*">>, CPENoVersion). xain_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"xain">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"xain">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:emetrotel:xain:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"xain">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"xain">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:emetrotel:xain:*:*:*:*:*:*:*:*">>, CPENoVersion). sweet_xml_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"sweet_xml">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"sweet_xml">>, <<"1.0.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:kbrw:sweet_xml:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"sweet_xml">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"sweet_xml">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:kbrw:sweet_xml:*:*:*:*:*:*:*:*">>, CPENoVersion). erlang_otp_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"erlang/otp">>, <<"28.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"erlang/otp">>, <<"28.0">>, undefined), ?assertEqual(<<"cpe:2.3:a:erlang:erlang\/otp:28.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"erlang/otp">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"erlang/otp">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:erlang:erlang\/otp:*:*:*:*:*:*:*:*">>, CPENoVersion). rebar3_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"rebar3">>, <<"3.14.1">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"rebar3">>, <<"3.14.1">>, undefined), ?assertEqual(<<"cpe:2.3:a:erlang:rebar3:3.14.1:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"rebar3">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"rebar3">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*">>, CPENoVersion). elixir_cpe_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"elixir">>, <<"1.19.3">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"elixir">>, <<"1.19.3">>, undefined), ?assertEqual(<<"cpe:2.3:a:elixir-lang:elixir:1.19.3:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"elixir">>, undefined, undefined), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"elixir">>, undefined, undefined), ?assertEqual(<<"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*">>, CPENoVersion). default_behavior_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"my_package">>, <<"1.0.0">>, <<"https://github.com/my_org/my_package">>), + CPE = rebar3_sbom_cpe:cpe(<<"my_package">>, <<"1.0.0">>, <<"https://github.com/my_org/my_package">>), ?assertEqual(<<"cpe:2.3:a:my_org:my_package:1.0.0:*:*:*:*:*:*:*">>, CPE), - CPENoVersion = rebar3_sbom_cpe:hex(<<"my_package">>, undefined, <<"https://github.com/my_org/my_package">>), + CPENoVersion = rebar3_sbom_cpe:cpe(<<"my_package">>, undefined, <<"https://github.com/my_org/my_package">>), ?assertEqual(<<"cpe:2.3:a:my_org:my_package:*:*:*:*:*:*:*:*">>, CPENoVersion). no_url_test(_) -> - CPE = rebar3_sbom_cpe:hex(<<"non_hex_package">>, <<"1.0.0">>, undefined), + CPE = rebar3_sbom_cpe:cpe(<<"non_hex_package">>, <<"1.0.0">>, undefined), ?assertEqual(undefined, CPE). diff --git a/test/rebar3_sbom_json_SUITE.erl b/test/rebar3_sbom_json_SUITE.erl index 7f5af3c..f066c80 100644 --- a/test/rebar3_sbom_json_SUITE.erl +++ b/test/rebar3_sbom_json_SUITE.erl @@ -44,6 +44,7 @@ -export([no_sbom_licenses_test/1]). -export([metadata_component_empty_links_cpe_test/1]). -export([external_references_fallback_test/1]). +-export([checkout_app_dependency_test/1]). % Includes -include_lib("common_test/include/ct.hrl"). @@ -125,7 +126,8 @@ groups() -> [{basic_app, [], [required_fields_test, {local_app, [], [no_sbom_manufacturer_test, no_sbom_licenses_test, metadata_component_empty_links_cpe_test, - external_references_fallback_test]}]. + external_references_fallback_test, + checkout_app_dependency_test]}]. init_per_suite(Config) -> application:load(rebar3_sbom), @@ -509,6 +511,20 @@ external_references_fallback_test(Config) -> #{<<"externalReferences">> := ExternalReferences} = Component, ?assertMatch([#{<<"type">> := <<"release-notes">>, <<"url">> := <<"https://example.com/changelog">>}], ExternalReferences). +checkout_app_dependency_test(Config) -> + SBoMJSON = ?config(sbom_json, Config), + #{<<"components">> := Components} = SBoMJSON, + Value = lists:search(fun(Component) -> + maps:get(<<"name">>, Component, undefined) =:= <<"checkout_app">> + end, Components), + ?assertMatch({value, _}, Value), + {_, Dependency} = Value, + check_component_constraints(Dependency), + ?assertMatch(#{<<"version">> := <<"0.1.0">>, + <<"name">> := <<"checkout_app">>, + <<"purl">> := <<"pkg:otp/checkout_app@0.1.0">>, + <<"licenses">> := _}, Dependency). + %--- Private ------------------------------------------------------------------- get_app_dir(DataDir, AppName) -> SplitDataDir = filename:split(DataDir), diff --git a/test/rebar3_sbom_purl_SUITE.erl b/test/rebar3_sbom_purl_SUITE.erl new file mode 100644 index 0000000..a76a73e --- /dev/null +++ b/test/rebar3_sbom_purl_SUITE.erl @@ -0,0 +1,78 @@ +-module(rebar3_sbom_purl_SUITE). + +% CT Exports +-export([all/0]). + +% Testcases +-export([hex_purl_test/1]). +-export([github_purl_test/1]). +-export([bitbucket_purl_test/1]). +-export([git_github_variants_test/1]). +-export([git_bitbucket_variants_test/1]). +-export([git_unsupported_host_test/1]). +-export([local_purl_test/1]). +-export([local_otp_app_purl_test/1]). + +% Includes +-include_lib("stdlib/include/assert.hrl"). + +%--- Common test functions ----------------------------------------------------- + +all() -> [hex_purl_test, + github_purl_test, + bitbucket_purl_test, + git_github_variants_test, + git_bitbucket_variants_test, + git_unsupported_host_test, + local_otp_app_purl_test, + local_purl_test]. + +%--- Test cases ---------------------------------------------------------------- + +hex_purl_test(_) -> + Purl = rebar3_sbom_purl:hex("Rebar3_SBOM", "1.2.3"), + ?assertEqual(<<"pkg:hex/rebar3_sbom@1.2.3">>, Purl). + +github_purl_test(_) -> + Purl = rebar3_sbom_purl:github("ExampleOrg/ExampleRepo", "1.0.0"), + ?assertEqual(<<"pkg:github/exampleorg/examplerepo@1.0.0">>, Purl). + +git_github_variants_test(_) -> + Urls = ["git@github.com:ExampleOrg/ExampleRepo.git", + "https://github.com/ExampleOrg/ExampleRepo.git", + "git://github.com/ExampleOrg/ExampleRepo.git"], + lists:foreach( + fun(Url) -> + Purl = rebar3_sbom_purl:git("example_app", Url, "3.0.0"), + ?assertEqual(<<"pkg:github/exampleorg/examplerepo@3.0.0">>, Purl) + end, + Urls). + +bitbucket_purl_test(_) -> + Purl = rebar3_sbom_purl:bitbucket("ExampleOrg/ExampleRepo", "2.0.0"), + ?assertEqual(<<"pkg:bitbucket/exampleorg/examplerepo@2.0.0">>, Purl). + +git_bitbucket_variants_test(_) -> + Urls = ["git@bitbucket.org:ExampleOrg/ExampleRepo.git", + "https://bitbucket.org/ExampleOrg/ExampleRepo.git", + "git://bitbucket.org/ExampleOrg/ExampleRepo.git"], + lists:foreach( + fun(Url) -> + Purl = rebar3_sbom_purl:git("example_app", Url, "4.0.0"), + ?assertEqual(<<"pkg:bitbucket/exampleorg/examplerepo@4.0.0">>, Purl) + end, + Urls). + +git_unsupported_host_test(_) -> + ?assertEqual(undefined, + rebar3_sbom_purl:git("example_app", + "git@gitlab.com:ExampleOrg/ExampleRepo.git", + "5.0.0")). + +local_otp_app_purl_test(_) -> + Purl = rebar3_sbom_purl:local_otp_app("Local-App", "0.9.0"), + ?assertEqual(<<"pkg:otp/local-app@0.9.0">>, Purl). + +local_purl_test(_) -> + Purl = rebar3_sbom_purl:local("Local-App", "0.9.0"), + ?assertEqual(<<"pkg:generic/local-app@0.9.0">>, Purl).