%% Test suite for the handling hexpm repo configurations -module(rebar_pkg_repos_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include("rebar.hrl"). all() -> [default_repo, repo_merging, repo_replacing, auth_merging, organization_merging, {group, resolve_version}]. groups() -> [{resolve_version, [use_first_repo_match, use_exact_with_hash, fail_repo_update, ignore_match_in_excluded_repo, optional_prereleases]}]. init_per_group(resolve_version, Config) -> Repo1 = <<"test-repo-1">>, Repo2 = <<"test-repo-2">>, Repo3 = <<"test-repo-3">>, Hexpm = <<"hexpm">>, Repos = [Repo1, Repo2, Repo3, Hexpm], Deps = [{"A", "0.1.1", <<"good checksum">>, Repo1, false}, {"A", "0.1.1", <<"good checksum">>, Repo2, false}, {"B", "1.0.0", Repo1, false}, {"B", "2.0.0", Repo2, false}, {"B", "1.4.0", Repo3, false}, {"B", "1.4.3", Hexpm, false}, {"B", "1.4.6", Hexpm, #{reason => 'RETIRED_INVALID'}}, {"B", "1.5.0", Hexpm, false}, {"B", "1.5.6-rc.0", Hexpm, true}, {"C", "1.3.1", <<"bad checksum">>, Repo1, false}, {"C", "1.3.1", <<"good checksum">>, Repo2, false}], [{deps, Deps}, {repos, Repos} | Config]; init_per_group(_, Config) -> Config. end_per_group(_, _) -> ok. init_per_testcase(use_first_repo_match, Config) -> Deps = ?config(deps, Config), Repos = ?config(repos, Config), State = setup_deps_and_repos(Deps, Repos), meck:new(rebar_packages, [passthrough, no_link]), %% fail when the first repo is updated since it doesn't have a matching package %% should continue anyway meck:expect(rebar_packages, update_package, fun(_, _, _State) -> ok end), meck:expect(rebar_packages, verify_table, fun(_State) -> true end), [{state, State} | Config]; init_per_testcase(use_exact_with_hash, Config) -> Deps = ?config(deps, Config), Repos = ?config(repos, Config), State = setup_deps_and_repos(Deps, Repos), meck:new(rebar_packages, [passthrough, no_link]), %% fail when the first repo is updated since it doesn't have a matching package %% should continue anyway meck:expect(rebar_packages, update_package, fun(_, _, _State) -> ok end), meck:expect(rebar_packages, verify_table, fun(_State) -> true end), [{state, State} | Config]; init_per_testcase(fail_repo_update, Config) -> Deps = ?config(deps, Config), Repos = ?config(repos, Config), State = setup_deps_and_repos(Deps, Repos), meck:new(rebar_packages, [passthrough, no_link]), %% fail when the first repo is updated since it doesn't have a matching package %% should continue anyway [Repo1 | _] = Repos, meck:expect(rebar_packages, update_package, fun(_, #{name := Repo}, _State) when Repo =:= Repo1 -> fail; (_, _, _State) -> ok end), meck:expect(rebar_packages, verify_table, fun(_State) -> true end), [{state, State} | Config]; init_per_testcase(ignore_match_in_excluded_repo, Config) -> Deps = ?config(deps, Config), Repos = [Repo1, _, Repo3 | _] = ?config(repos, Config), %% drop repo1 and repo2 from the repos to be used by the pkg resource State = setup_deps_and_repos(Deps, [R || R <- Repos, R =/= Repo3, R =/= Repo1]), meck:new(rebar_packages, [passthrough, no_link]), %% fail when the first repo is updated since it doesn't have a matching package %% should continue anyway [_, _, Repo3 | _] = Repos, meck:expect(rebar_packages, update_package, fun(_, _, _State) -> ok end), meck:expect(rebar_packages, verify_table, fun(_State) -> true end), [{state, State} | Config]; init_per_testcase(optional_prereleases, Config) -> Deps = ?config(deps, Config), Repos = ?config(repos, Config), State = setup_deps_and_repos(Deps, Repos), meck:new(rebar_packages, [passthrough, no_link]), meck:expect(rebar_packages, update_package, fun(_, _, _State) -> ok end), meck:expect(rebar_packages, verify_table, fun(_State) -> true end), [{state, State} | Config]; init_per_testcase(auth_merging, Config) -> meck:new(file, [passthrough, no_link, unstick]), meck:new(rebar_packages, [passthrough, no_link]), Config; init_per_testcase(organization_merging, Config) -> meck:new(file, [passthrough, no_link, unstick]), meck:new(rebar_packages, [passthrough, no_link]), Config; init_per_testcase(_, Config) -> Config. end_per_testcase(Case, _Config) when Case =:= auth_merging ; Case =:= organization_merging -> meck:unload(file), meck:unload(rebar_packages); end_per_testcase(Case, _Config) when Case =:= use_first_repo_match ; Case =:= use_exact_with_hash ; Case =:= fail_repo_update ; Case =:= ignore_match_in_excluded_repo ; Case =:= optional_prereleases -> meck:unload(rebar_packages); end_per_testcase(_, _) -> ok. default_repo(_Config) -> Repo1 = #{name => <<"hexpm">>, api_key => <<"asdf">>}, MergedRepos = rebar_hex_repos:repos([{repos, [Repo1]}]), ?assertMatch([#{name := <<"hexpm">>, api_key := <<"asdf">>, api_url := <<"https://hex.pm/api">>}], MergedRepos). repo_merging(_Config) -> Repo1 = #{name => <<"repo-1">>, api_url => <<"repo-1/api">>}, Repo2 = #{name => <<"repo-2">>, repo_url => <<"repo-2/repo">>, repo_verify => false}, Result = rebar_hex_repos:merge_repos([Repo1, Repo2, #{name => <<"repo-2">>, api_url => <<"repo-2/api">>, repo_url => <<"bad url">>, repo_verify => true}, #{name => <<"repo-1">>, api_url => <<"bad url">>, repo_verify => true}, #{name => <<"repo-2">>, api_url => <<"repo-2/api-2">>, repo_url => <<"other/repo">>}]), ?assertMatch([#{name := <<"repo-1">>, api_url := <<"repo-1/api">>, repo_verify := true}, #{name := <<"repo-2">>, api_url := <<"repo-2/api">>, repo_url := <<"repo-2/repo">>, repo_verify := false}], Result). repo_replacing(_Config) -> Repo1 = #{name => <<"repo-1">>, repo_name => <<"repo-1">>, api_url => <<"repo-1/api">>}, Repo2 = #{name => <<"repo-2">>, repo_name => <<"repo-2">>, repo_url => <<"repo-2/repo">>, repo_verify => false}, ?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}], rebar_hex_repos:repos([{repos, [Repo1]}, {repos, [Repo2]}])), %% use of replace is ignored if found in later entries than the first ?assertMatch([Repo1, Repo2, #{name := <<"hexpm">>}], rebar_hex_repos:repos([{repos, [Repo1]}, {repos, replace, [Repo2]}])), ?assertMatch([Repo1], rebar_hex_repos:repos([{repos, replace, [Repo1]}, {repos, [Repo2]}])). auth_merging(_Config) -> Repo1 = #{name => <<"repo-1">>, api_url => <<"repo-1/api">>}, Repo2 = #{name => <<"repo-2">>, repo_url => <<"repo-2/repo">>, repo_verify => false}, State = rebar_state:new([{hex, [{repos, [Repo1, Repo2]}]}]), meck:expect(file, consult, fun(_) -> {ok, [#{<<"repo-1">> => #{read_key => <<"read key">>, write_key => <<"write key">>}, <<"repo-2">> => #{read_key => <<"read key 2">>, repos_key => <<"repos key 2">>, write_key => <<"write key 2">>}, <<"hexpm">> => #{write_key => <<"write key hexpm">>}}]} end), ?assertMatch({ok, #resource{state=#{repos := [#{name := <<"repo-1">>, read_key := <<"read key">>, write_key := <<"write key">>}, #{name := <<"repo-2">>, read_key := <<"read key 2">>, repos_key := <<"repos key 2">>, write_key := <<"write key 2">>}, #{name := <<"hexpm">>, write_key := <<"write key hexpm">>}]}}}, rebar_pkg_resource:init(pkg, State)), ok. organization_merging(_Config) -> Repo1 = #{name => <<"hexpm:repo-1">>, api_url => <<"repo-1/api">>}, Repo2 = #{name => <<"hexpm:repo-2">>, repo_url => <<"repo-2/repo">>, repo_verify => false}, State = rebar_state:new([{hex, [{repos, [Repo1, Repo2]}]}]), meck:expect(file, consult, fun(_) -> {ok, [#{<<"hexpm:repo-1">> => #{read_key => <<"read key">>}, <<"hexpm:repo-2">> => #{read_key => <<"read key 2">>, repos_key => <<"repos key 2">>, write_key => <<"write key 2">>}, <<"hexpm">> => #{write_key => <<"write key hexpm">>}}]} end), ?assertMatch({ok, #resource{state=#{repos := [#{name := <<"hexpm:repo-1">>, parent := <<"hexpm">>, read_key := <<"read key">>, write_key := <<"write key hexpm">>}, #{name := <<"hexpm:repo-2">>, parent := <<"hexpm">>, read_key := <<"read key 2">>, repos_key := <<"repos key 2">>, write_key := <<"write key 2">>}, #{name := <<"hexpm">>, write_key := <<"write key hexpm">>}]}}}, rebar_pkg_resource:init(pkg, State)), ok. use_first_repo_match(Config) -> State = ?config(state, Config), ?assertMatch({ok,{package,{<<"B">>, {{2,0,0}, {[],[]}}, Repo2}, <<"some checksum">>, false, []}, #{name := Repo2, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"> 1.4.0">>, undefined, ?PACKAGE_TABLE, State)), ?assertMatch({ok,{package,{<<"B">>, {{1,4,0}, {[],[]}}, Repo3}, <<"some checksum">>, false, []}, #{name := Repo3, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, ?PACKAGE_TABLE, State)). %% tests that even though an easier repo has C-1.3.1 it doesn't use it since its hash is different use_exact_with_hash(Config) -> State = ?config(state, Config), ?assertMatch({ok,{package,{<<"C">>, {{1,3,1}, {[],[]}}, Repo2}, <<"good checksum">>, false, []}, #{name := Repo2, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"C">>, <<"1.3.1">>, <<"good checksum">>, ?PACKAGE_TABLE, State)). fail_repo_update(Config) -> State = ?config(state, Config), ?assertMatch({ok,{package,{<<"B">>, {{1,4,0}, {[],[]}}, Repo3}, <<"some checksum">>, false, []}, #{name := Repo3, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, ?PACKAGE_TABLE, State)). ignore_match_in_excluded_repo(Config) -> State = ?config(state, Config), Repos = ?config(repos, Config), ?assertMatch({ok,{package,{<<"B">>, {{1,4,6}, {[],[]}}, Hexpm}, <<"some checksum">>, #{reason := 'RETIRED_INVALID'}, []}, #{name := Hexpm, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, ?PACKAGE_TABLE, State)), [_, Repo2 | _] = Repos, ?assertMatch({ok,{package,{<<"A">>, {{0,1,1}, {[],[]}}, Repo2}, <<"good checksum">>, false, []}, #{name := Repo2, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"A">>, <<"0.1.1">>, <<"good checksum">>, ?PACKAGE_TABLE, State)). optional_prereleases(Config) -> State = ?config(state, Config), ?assertMatch({ok,{package,{<<"B">>, {{1,5,0}, {[],[]}}, Hexpm}, <<"some checksum">>, false, []}, #{name := Hexpm, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"~> 1.5.0">>, undefined, ?PACKAGE_TABLE, State)), ?assertMatch({ok,{package,{<<"B">>, {{1,5,6}, {[<<"rc">>,0],[]}}, Hexpm}, <<"some checksum">>, true, []}, #{name := Hexpm, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"1.5.6-rc.0">>, <<"some checksum">>, ?PACKAGE_TABLE, State)), %% allow prerelease through configuration State1 = rebar_state:set(State, deps_allow_prerelease, true), ?assertMatch({ok,{package,{<<"B">>, {{1,5,6}, {[<<"rc">>,0],[]}}, Hexpm}, <<"some checksum">>, true, []}, #{name := Hexpm, http_adapter_config := #{profile := rebar}}}, rebar_packages:resolve_version(<<"B">>, <<"~> 1.5.0">>, <<"some checksum">>, ?PACKAGE_TABLE, State1)). %% setup_deps_and_repos(Deps, Repos) -> catch ets:delete(?PACKAGE_TABLE), true = rebar_packages:new_package_table(), insert_deps(Deps), State = rebar_state:new([{hex, [{repos, [#{name => R} || R <- Repos]}]}]), rebar_state:create_resources([{pkg, rebar_pkg_resource}], State). insert_deps(Deps) -> lists:foreach(fun({Name, Version, Repo, Retired}) -> ets:insert(?PACKAGE_TABLE, #package{key={rebar_utils:to_binary(Name), ec_semver:parse(Version), rebar_utils:to_binary(Repo)}, dependencies=[], retired=Retired, checksum = <<"some checksum">>}); ({Name, Version, Checksum, Repo, Retired}) -> ets:insert(?PACKAGE_TABLE, #package{key={rebar_utils:to_binary(Name), ec_semver:parse(Version), rebar_utils:to_binary(Repo)}, dependencies=[], retired=Retired, checksum = Checksum}) end, Deps).