Add test/hk_search_job_tests.erl
Browse files- test/hk_search_job_tests.erl +68 -0
test/hk_search_job_tests.erl
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
%%%-------------------------------------------------------------------
|
| 2 |
+
%%% @doc Covers "search orchestration" from `<test>': runs the full
|
| 3 |
+
%%% `<search_pipeline>' end-to-end against the bundled mock provider
|
| 4 |
+
%%% (no network required) and asserts every stage's event fires, in
|
| 5 |
+
%%% pipeline order, and the job reaches `done' with synthesized
|
| 6 |
+
%%% results.
|
| 7 |
+
%%% @end
|
| 8 |
+
%%%-------------------------------------------------------------------
|
| 9 |
+
-module(hk_search_job_tests).
|
| 10 |
+
-include_lib("eunit/include/eunit.hrl").
|
| 11 |
+
|
| 12 |
+
setup() -> hk_test_helper:start_app().
|
| 13 |
+
teardown(Ok) -> hk_test_helper:stop_app(Ok).
|
| 14 |
+
|
| 15 |
+
search_fixture_test_() ->
|
| 16 |
+
{timeout, 20, {setup, fun setup/0, fun teardown/1, fun(_) -> [
|
| 17 |
+
{"a search job runs every pipeline stage in order and completes",
|
| 18 |
+
fun full_pipeline_runs_in_order/0}
|
| 19 |
+
] end}}.
|
| 20 |
+
|
| 21 |
+
full_pipeline_runs_in_order() ->
|
| 22 |
+
ok = hk_event_bus:subscribe(#{category_prefix => <<"search.">>}),
|
| 23 |
+
{ok, JobId} = hk_search:start(undefined, <<"erlang otp supervision">>, #{max_iterations => 2}),
|
| 24 |
+
Categories = collect_until(<<"search.completed">>, JobId, [], 10000),
|
| 25 |
+
ok = hk_event_bus:unsubscribe(),
|
| 26 |
+
|
| 27 |
+
?assertEqual(<<"search.started">>, hd(Categories)),
|
| 28 |
+
?assertEqual(<<"search.completed">>, lists:last(Categories)),
|
| 29 |
+
%% The pipeline stage events must appear in the same relative order
|
| 30 |
+
%% every time (deterministic orchestration) -- assert each of these
|
| 31 |
+
%% appears, and that it appears before the next one in the list.
|
| 32 |
+
ExpectedOrder = [<<"search.started">>, <<"search.query_generated">>,
|
| 33 |
+
<<"search.provider_called">>, <<"search.results_received">>,
|
| 34 |
+
<<"search.urls_deduplicated">>, <<"search.document_retrieved">>,
|
| 35 |
+
<<"search.result_synthesized">>, <<"search.completed">>],
|
| 36 |
+
assert_relative_order(ExpectedOrder, Categories),
|
| 37 |
+
|
| 38 |
+
{ok, Pid} = hk_search_registry:lookup(JobId),
|
| 39 |
+
{ok, Result} = hk_search_job:await_result(Pid, 1000),
|
| 40 |
+
?assert(maps:is_key(results, Result)),
|
| 41 |
+
?assert(maps:is_key(sources, Result)).
|
| 42 |
+
|
| 43 |
+
%%% internal
|
| 44 |
+
|
| 45 |
+
collect_until(TargetCategory, JobId, Acc, TimeoutMs) ->
|
| 46 |
+
receive
|
| 47 |
+
{hk_event, #{category := TargetCategory, data := #{job_id := JobId}}} ->
|
| 48 |
+
lists:reverse([TargetCategory | Acc]);
|
| 49 |
+
{hk_event, #{category := Cat, data := #{job_id := JobId}}} ->
|
| 50 |
+
collect_until(TargetCategory, JobId, [Cat | Acc], TimeoutMs);
|
| 51 |
+
{hk_event, _Other} ->
|
| 52 |
+
collect_until(TargetCategory, JobId, Acc, TimeoutMs)
|
| 53 |
+
after TimeoutMs ->
|
| 54 |
+
lists:reverse(Acc)
|
| 55 |
+
end.
|
| 56 |
+
|
| 57 |
+
assert_relative_order([_Last], _Categories) ->
|
| 58 |
+
ok;
|
| 59 |
+
assert_relative_order([A, B | Rest], Categories) ->
|
| 60 |
+
IdxA = index_of(A, Categories),
|
| 61 |
+
IdxB = index_of(B, Categories),
|
| 62 |
+
?assert(IdxA =/= not_found andalso IdxB =/= not_found andalso IdxA < IdxB),
|
| 63 |
+
assert_relative_order([B | Rest], Categories).
|
| 64 |
+
|
| 65 |
+
index_of(X, List) -> index_of(X, List, 1).
|
| 66 |
+
index_of(_X, [], _N) -> not_found;
|
| 67 |
+
index_of(X, [X | _], N) -> N;
|
| 68 |
+
index_of(X, [_ | T], N) -> index_of(X, T, N + 1).
|