rebar3/test/rebar_pkg_repos_SUITE.erl

490 lines
22 KiB
Erlang

%% 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_read_write_read, auth_remove_from_config, auth_merging, auth_config_errors, 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", <<"inner checksum">>, <<"good outer checksum">>, Repo1, false},
{"A", "0.1.1", <<"inner checksum">>, <<"good outer 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", <<"inner checksum">>, <<"bad outer checksum">>, Repo1, false},
{"C", "1.3.1", <<"inner checksum">>, <<"good outer 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(Case, Config) when Case =:= auth_merging ;
Case =:= auth_config_errors ;
Case =:= auth_remove_from_config ;
Case =:= auth_read_write_read ->
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 =:= auth_config_errors ;
Case =:= organization_merging ;
Case =:= auth_remove_from_config ;
Case =:= auth_read_write_read ->
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.
auth_config_errors(_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(_) ->
{error, {3,erl_parse,["syntax error before: ","'=>'"]}}
end),
?assertThrow(rebar_abort, rebar_pkg_resource:init(pkg, State)),
meck:expect(file, consult,
fun(_) ->
{error, enoent}
end),
{ok, #resource{state=#{ repos := [
UpdatedRepo1,
UpdatedRepo2,
DefaultRepo
]}}} = rebar_pkg_resource:init(pkg, State),
?assertEqual(undefined, maps:get(write_key, UpdatedRepo1, undefined)),
?assertEqual(undefined, maps:get(read_key, UpdatedRepo1, undefined)),
?assertEqual(undefined, maps:get(repos_key, UpdatedRepo1, undefined)),
?assertEqual(undefined, maps:get(write_key, UpdatedRepo2, undefined)),
?assertEqual(undefined, maps:get(repos_key, UpdatedRepo2, undefined)),
?assertEqual(undefined, maps:get(read_key, UpdatedRepo2, undefined)),
?assertEqual(undefined, maps:get(write_key, DefaultRepo, undefined)),
ok.
auth_read_write_read(_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">>}}]}
end),
meck:expect(file, write_file,
fun(_File, CfgBin, _) ->
case binary:match(CfgBin, <<"foo">>) of
nomatch ->
Err = "expected auth config binary with key <<\"foo\">>",
meck:exception(error, {expected, Err});
_ ->
ok
end
end),
rebar_hex_repos:update_auth_config(#{<<"foo">> => <<200>>}, State).
auth_remove_from_config(_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">>}}]}
end),
meck:expect(file, write_file,
fun(_File, CfgBin, _) ->
case binary:match(CfgBin, <<"hexpm:repo-1">>) of
nomatch ->
ok;
_ ->
Err = "expected auth config binary without key <<\"hexpm:repo-1\">>",
meck:exception(error, {expected, Err})
end
end),
rebar_hex_repos:remove_from_auth_config(<<"hexpm:repo-1">>, State).
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">>,
repo_name := <<"repo-1">>,
api_repository := <<"repo-1">>,
repo_organization := <<"repo-1">>,
read_key := <<"read key">>,
write_key := <<"write key hexpm">>},
#{name := <<"hexpm:repo-2">>,
parent := <<"hexpm">>,
repo_name := <<"repo-2">>,
api_repository := <<"repo-2">>,
repo_organization := <<"repo-2">>,
read_key := <<"read key 2">>,
repos_key := <<"repos key 2">>,
write_key := <<"write key 2">>},
#{name := <<"hexpm">>,
repo_name := <<"hexpm">>,
api_repository := undefined,
repo_organization := undefined,
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},
<<"inner checksum">>,<<"outer checksum">>, false, []},
#{name := Repo2,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"> 1.4.0">>, undefined, undefined,
?PACKAGE_TABLE, State)),
?assertMatch({ok,{package,{<<"B">>, {{1,4,0}, {[],[]}}, Repo3},
<<"inner checksum">>,<<"outer checksum">>, false, []},
#{name := Repo3,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, 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},
<<"inner checksum">>, <<"good outer checksum">>, false, []},
#{name := Repo2,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"C">>, <<"1.3.1">>, <<"inner checksum">>, <<"good outer checksum">>,
?PACKAGE_TABLE, State)).
fail_repo_update(Config) ->
State = ?config(state, Config),
?assertMatch({ok,{package,{<<"B">>, {{1,4,0}, {[],[]}}, Repo3},
<<"inner checksum">>,<<"outer checksum">>, false, []},
#{name := Repo3,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, 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},
<<"inner checksum">>,<<"outer checksum">>, #{reason := 'RETIRED_INVALID'}, []},
#{name := Hexpm,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.4.0">>, undefined, undefined,
?PACKAGE_TABLE, State)),
[_, Repo2 | _] = Repos,
?assertMatch({ok,{package,{<<"A">>, {{0,1,1}, {[],[]}}, Repo2},
<<"inner checksum">>, <<"good outer checksum">>, false, []},
#{name := Repo2,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"A">>, <<"0.1.1">>, <<"inner checksum">>, <<"good outer checksum">>,
?PACKAGE_TABLE, State)).
optional_prereleases(Config) ->
State = ?config(state, Config),
?assertMatch({ok,{package,{<<"B">>, {{1,5,0}, {[],[]}}, Hexpm},
<<"inner checksum">>,<<"outer checksum">>, false, []},
#{name := Hexpm,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.5.0">>, undefined, undefined,
?PACKAGE_TABLE, State)),
?assertMatch({ok,{package,{<<"B">>, {{1,5,6}, {[<<"rc">>,0],[]}}, Hexpm},
<<"inner checksum">>,<<"outer checksum">>, true, []},
#{name := Hexpm,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"1.5.6-rc.0">>, <<"inner checksum">>, <<"outer 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},
<<"inner checksum">>,<<"outer checksum">>, true, []},
#{name := Hexpm,
http_adapter := {rebar_httpc_adapter, #{profile := rebar}}}},
rebar_packages:resolve_version(<<"B">>, <<"~> 1.5.0">>, <<"inner checksum">>, <<"outer 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,
inner_checksum = <<"inner checksum">>,
outer_checksum = <<"outer checksum">>});
({Name, Version, InnerChecksum, OuterChecksum, 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,
inner_checksum = InnerChecksum,
outer_checksum = OuterChecksum})
end, Deps).