Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matchers v1 #23

Merged
merged 34 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
dd60726
Matchers v1
silverblaze404 Feb 23, 2024
750e1b3
Added formatting
silverblaze404 Feb 23, 2024
57ff83a
More tests
silverblaze404 Feb 23, 2024
663089b
updated readme and changelog
silverblaze404 Feb 23, 2024
37ed0a3
Minor fix
silverblaze404 Feb 23, 2024
5382e2f
Added more tests
silverblaze404 Feb 24, 2024
45da2c1
Review comments
silverblaze404 Mar 7, 2024
49ec30a
fix
silverblaze404 Mar 7, 2024
3aab2c4
fix
silverblaze404 Mar 7, 2024
253e407
fixed specs
silverblaze404 Mar 7, 2024
24bc1ca
Fixed readme and minor issues
silverblaze404 Mar 7, 2024
01602f1
Fixed is_map call
silverblaze404 Mar 7, 2024
54fb238
fix
silverblaze404 Mar 7, 2024
5cf7dad
fix
silverblaze404 Mar 7, 2024
56afd2a
fix
silverblaze404 Mar 7, 2024
830346c
fix
silverblaze404 Mar 7, 2024
a0efbf5
fix
silverblaze404 Mar 7, 2024
14b817a
fix
silverblaze404 Mar 7, 2024
7c7aff2
fix format
silverblaze404 Mar 7, 2024
ea2b9ef
more tests
silverblaze404 Mar 8, 2024
96fe4e8
test fix
silverblaze404 Mar 8, 2024
763bdb9
test fix
silverblaze404 Mar 8, 2024
6114a27
test fix
silverblaze404 Mar 8, 2024
2ea5fc8
using regex match instead of like
silverblaze404 Mar 8, 2024
5062768
fixed test cases
silverblaze404 Mar 8, 2024
1143655
fixed formatting
silverblaze404 Mar 8, 2024
d6c1311
fix tests
silverblaze404 Mar 8, 2024
c10b60f
fix tests
silverblaze404 Mar 8, 2024
e742b21
fix minor issue
silverblaze404 Mar 8, 2024
55dbd35
fix minor issue
silverblaze404 Mar 8, 2024
3099182
remove repeated code block
silverblaze404 Mar 8, 2024
7452f02
Updated readme
silverblaze404 Mar 8, 2024
4af6cdc
Updated readme again
silverblaze404 Mar 8, 2024
fb4dafd
Removed comments
silverblaze404 Mar 8, 2024
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added

- Introduced ability to add params inside given in pact interaction
- Introduced basic pact matchers


## [0.2.2] - 2023-11-03

Expand Down
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ PactRef = pact:v4(<<"consumer">>, <<"producer">>).
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => jsx:encode(#{users => [#{user_id => 1, user_name => <<"ranjan">>, age => 26}]})
body => #{users => [#{user_id => 1, user_name => <<"ranjan">>, age => 26}]}
}
}).

Expand All @@ -81,8 +81,27 @@ pact:cleanup(PactRef).
Matching request path and request/response headers, and body values
-----

Easy-to-use matchers module is not implemented yet, but matchers can be used by manually specifying a matcher string.
Check the possible values here: https://github.com/pact-foundation/pact-reference/blob/master/rust/pact_ffi/IntegrationJson.md
```erlang
%% Alternatively, you can also match things inside each request/response
pact:interaction(PactRef,
#{
upon_receiving => <<"a request to create an animal: Lazgo">>,
with_request => #{
method => <<"POST">>,
path => <<"/animals">>,
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => #{
<<"name">> => pact:like(<<"Lazgo">>),
<<"type">> => pact:like(<<"dog">>)
}
},
will_respond_with => #{
status => 201
}
})
```

Release Checklist
-----
Expand Down
28 changes: 27 additions & 1 deletion src/pact.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
verify/1,
write/1,
write/2,
cleanup/1
cleanup/1,
like/1,
each_like/1,
regex_match/2,
each_key/2
]).

-type consumer() :: binary().
Expand Down Expand Up @@ -47,3 +51,25 @@ write(PactPid, Path) ->
-spec cleanup(pact_pid()) -> ok.
cleanup(PactPid) ->
pact_consumer:cleanup(PactPid).

%% @doc Matches all the child objects (and their child objects etc.)
%% Matched according to their types
-spec like(any()) -> map().
like(Term) ->
pact_matchers:like(Term).

%% @doc Asserts the Term is an array type that consists of elements
%% Like the one passed in
-spec each_like(any()) -> map().
each_like(Term) ->
pact_matchers:each_like(Term).

%% @doc Asserts the value should match the given regular expression
-spec regex_match(binary() | boolean() | number(), binary()) -> map().
regex_match(Value, Regex) ->
pact_matchers:regex_match(Value, Regex).

%% @doc Asserts that each key of Value should match the given regular expression
-spec each_key(binary() | boolean() | number(), binary()) -> map().
each_key(Value, Regex) ->
pact_matchers:each_key(Value, Regex).
46 changes: 38 additions & 8 deletions src/pact_consumer_http.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,38 @@ start_mock_server(PactPid, PactRef, Host, Port, InteractionPart) ->

-spec insert_request_details(pact_interaction_ref(), request_details()) -> ok.
insert_request_details(InteractionRef, RequestDetails) ->
%% Checking if someone used regex_match
CheckIfMap =
fun(Value) ->
case is_map(Value) of
true ->
thoas:encode(Value);
false ->
Value
end
end,
ReqMethod = maps:get(method, RequestDetails),
ReqPath = maps:get(path, RequestDetails),
pactffi_nif:with_request(InteractionRef, ReqMethod, ReqPath),
NewReqPath = CheckIfMap(ReqPath),
pactffi_nif:with_request(InteractionRef, ReqMethod, NewReqPath),
ReqHeaders = maps:get(headers, RequestDetails, #{}),
ContentType = get_content_type(ReqHeaders),
maps:fold(
fun(Key, Value, _Acc) ->
%% FIXME: 4th parameter is Index.. need to increment
pactffi_nif:with_header_v2(InteractionRef, 0, Key, 0, Value)
NewValue = CheckIfMap(Value),
pactffi_nif:with_header_v2(InteractionRef, 0, Key, 0, NewValue)
end,
ok,
ReqHeaders
),
ReqBody = maps:get(body, RequestDetails, undefined),
case ReqBody of
undefined -> ok;
_ -> pactffi_nif:with_body(InteractionRef, 0, ContentType, ReqBody)
undefined ->
ok;
_ ->
NewReqBody = CheckIfMap(ReqBody),
pactffi_nif:with_body(InteractionRef, 0, ContentType, NewReqBody)
end,
ReqQueryParams = maps:get(query_params, RequestDetails, undefined),
case ReqQueryParams of
Expand All @@ -105,7 +120,8 @@ insert_request_details(InteractionRef, RequestDetails) ->
maps:fold(
fun(Key, Value, _Acc) ->
%% FIXME: 3rd parameter is Index.. need to increment
pactffi_nif:with_query_parameter_v2(InteractionRef, Key, 0, Value)
NewValue = CheckIfMap(Value),
pactffi_nif:with_query_parameter_v2(InteractionRef, Key, 0, NewValue)
end,
ok,
ReqQueryParams
Expand All @@ -115,6 +131,16 @@ insert_request_details(InteractionRef, RequestDetails) ->

-spec insert_response_details(pact_interaction_ref(), response_details()) -> ok.
insert_response_details(InteractionRef, ResponseDetails) ->
%% Checking if someone used regex_match
CheckIfMap =
fun(Value) ->
case is_map(Value) of
true ->
thoas:encode(Value);
false ->
Value
end
end,
ResponseStatusCode = maps:get(status, ResponseDetails, undefined),
case ResponseStatusCode of
undefined -> ok;
Expand All @@ -125,15 +151,19 @@ insert_response_details(InteractionRef, ResponseDetails) ->
maps:fold(
fun(Key, Value, _Acc) ->
%% FIXME: 4th parameter is Index.. need to increment
pactffi_nif:with_header_v2(InteractionRef, 1, Key, 0, Value)
NewValue = CheckIfMap(Value),
pactffi_nif:with_header_v2(InteractionRef, 1, Key, 0, NewValue)
end,
ok,
ResHeaders
),
ResBody = maps:get(body, ResponseDetails, undefined),
case ResBody of
undefined -> ok;
_ -> pactffi_nif:with_body(InteractionRef, 1, ContentType, ResBody)
undefined ->
ok;
_ ->
NewResBody = CheckIfMap(ResBody),
pactffi_nif:with_body(InteractionRef, 1, ContentType, NewResBody)
end,
ok.

Expand Down
84 changes: 84 additions & 0 deletions src/pact_matchers.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
-module(pact_matchers).

-export([
like/1,
each_like/1,
each_key/2,
regex_match/2
]).

%% @doc Function for matching with type of the given term
-spec like(binary() | boolean() | number() | list() | map()) -> map().
like(Term) when (is_number(Term) orelse is_binary(Term) orelse is_boolean(Term)) ->
#{
<<"value">> => Term,
<<"pact:matcher:type">> => <<"type">>
};
like(Term) when (is_list(Term)) ->
lists:foldr(
fun(Elem, Acc) ->
[?MODULE:like(Elem) | Acc]
end,
[],
Term
);
like(Term) when (is_map(Term)) ->
KeyPresent = maps:get(<<"pact:matcher:type">>, Term, undefined),
case KeyPresent of
undefined ->
maps:map(
fun(_Key, InitValue) ->
?MODULE:like(InitValue)
end,
Term
);
_ ->
Term
end.

%% @doc Function for matching each entity inside a list with type of given term
-spec each_like(binary() | boolean() | number() | map() | list()) -> map().
each_like(Term) when (is_number(Term) orelse is_binary(Term) orelse is_boolean(Term)) ->
#{
<<"value">> => [Term],
<<"pact:matcher:type">> => <<"type">>
};
each_like(Term) when (is_list(Term)) ->
List =
lists:foldr(
fun(Elem, Acc) ->
[?MODULE:like(Elem) | Acc]
end,
[],
Term
),
#{
<<"value">> => [List],
<<"pact:matcher:type">> => <<"type">>
};
each_like(Term) when (is_map(Term)) ->
Map = ?MODULE:like(Term),
#{
<<"value">> => [Map],
<<"pact:matcher:type">> => <<"type">>
}.

%% @doc Function for matching with regex
-spec regex_match(binary() | boolean() | number() | map() | list(), binary()) -> map().
regex_match(Value, Regex) ->
#{
<<"value">> => Value,
<<"pact:matcher:type">> => <<"regex">>,
<<"regex">> => Regex
}.

%% @doc Function for matching each key inside a map with regex
-spec each_key(binary() | boolean() | number() | map() | list(), binary()) -> map().
each_key(Value, Regex) ->
#{
<<"value">> => Value,
<<"pact:matcher:type">> => <<"eachKey">>,
<<"rules">> => [
?MODULE:regex_match(<<"">>, Regex)
]
}.
39 changes: 33 additions & 6 deletions test/pact_end_to_end_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ get_animal_success(Config) ->
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => thoas:encode(AnimalObject)
body => AnimalObject
}
}),
?assertMatch({ok, AnimalObject}, animal_service_interface:get_animal(Port, "Mary")),
Expand Down Expand Up @@ -88,14 +88,14 @@ get_animal_failure(Config) ->
upon_receiving => <<"a request to GET a non-existing animal: Miles">>,
with_request => #{
method => <<"GET">>,
path => <<"/animals/Miles">>
path => pact:regex_match(<<"/animals/Miles">>, <<"^\/animals\/[a-zA-Z]+">>)
},
will_respond_with => #{
status => 404,
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => thoas:encode(#{error => not_found})
body => #{error => not_found}
}
}),
?assertMatch({error, not_found}, animal_service_interface:get_animal(Port, "Miles")),
Expand All @@ -104,7 +104,34 @@ get_animal_failure(Config) ->

create_animal(Config) ->
PactRef = ?config(pact_ref, Config),
AnimalObject = #{<<"name">> => <<"Max">>, <<"type">> => <<"dog">>},
AnimalPactObject = pact:like(#{
<<"name">> => <<"Max">>,
<<"type">> => <<"dog">>,
<<"age">> => 3,
<<"nickname">> => <<"koko">>,
<<"weight_kg">> => 12.0,
<<"gender">> => pact:regex_match(<<"male">>, <<"(male|female)">>),
<<"carnivorous">> => true,
<<"siblings">> => pact:each_like(<<"lola">>),
<<"list_att">> => pact:each_like([1,pact:like(<<"head">>)]),
<<"children">> => [<<"coco">>],
<<"children_details">> => pact:each_like(#{<<"name">> => <<"coco">>, <<"age">> => 1, <<"body_size">> => [3,4,5]}),
<<"attributes">> => pact:each_key(#{<<"happy">> => true}, <<"(happy|ferocious)">>)
}),
AnimalObject = [
{<<"name">>, <<"Max">>},
{<<"type">>, <<"dog">>},
{<<"age">>, 3},
{<<"nickname">>, <<"lazgo">>},
{<<"weight_kg">>, 10.0},
{<<"gender">>, <<"male">>},
{<<"carnivorous">>, true},
{<<"siblings">>, [<<"lola">>, <<"mary">>]},
{<<"list_att">>, [[2, <<"legs">>]]},
{<<"children">>, [<<"coco">>]},
{<<"children_details">>, [[{<<"name">>, <<"coco">>}, {<<"age">>, 1}, {<<"body_size">>, [3,4,5]}]]},
{<<"attributes">>, [{<<"ferocious">>, false}]}
],
{ok, Port} = pact:interaction(PactRef,
#{
upon_receiving => <<"a request to create an animal: Max">>,
Expand All @@ -114,7 +141,7 @@ create_animal(Config) ->
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => thoas:encode(AnimalObject)
body => AnimalPactObject
},
will_respond_with => #{
status => 201
Expand Down Expand Up @@ -144,7 +171,7 @@ search_animals(Config) ->
headers => #{
<<"Content-Type">> => <<"application/json">>
},
body => thoas:encode(Result)
body => Result
}
}),
?assertMatch({ok, Result}, animal_service_interface:search_animals(Port, Query)),
Expand Down
Loading