Fosi Fossil Service
Small teams. Open source. Built on Fossil.

CaaS / Lamina

Unified Management for Fossil Repositories

Public | Owner: CaaS | Default branch: trunk | Last update: 2026-10-25 00:41

Artifact e87d29451afc9697e381201adbd88f2c44d0479f885602bf080c1dc54681cd0c:


-module(nova_handler_tests).
-include_lib("eunit/include/eunit.hrl").
-include("../include/nova_router.hrl").

-define(SETUP, fun() -> setup() end).
-define(CLEANUP, fun(S) -> cleanup(S) end).

setup() ->
    Prev = nova_test_helper:setup_nova_env(),
    application:set_env(nova, dispatch_backend, persistent_term),
    application:set_env(nova, render_error_pages, false),
    %% Set up dispatch table for render_response/3
    Tree = nova_routing_trie:new(#{use_strict => false, convert_to_binary => true}),
    persistent_term:put(nova_dispatch, Tree),
    persistent_term:put(nova_use_stacktrace, false),
    %% Start nova_handlers gen_server (registers ETS table + default handlers)
    {ok, Pid} = nova_handlers:start_link(),
    %% Need meck for cowboy_req:reply
    meck:new(cowboy_req, [passthrough, no_link]),
    meck:expect(cowboy_req, reply, fun(Status, Req) ->
        Req#{resp_status_code => Status}
    end),
    meck:expect(cowboy_req, reply, fun(Status, Headers, Body, Req) ->
        Req#{resp_status_code => Status, resp_headers => Headers, resp_body => Body}
    end),
    meck:expect(cowboy_req, set_resp_header, fun(Name, Value, Req) ->
        Headers = maps:get(resp_headers, Req, #{}),
        Req#{resp_headers => Headers#{Name => Value}}
    end),
    {Prev, Pid}.

cleanup({Prev, Pid}) ->
    meck:unload(cowboy_req),
    gen_server:stop(Pid),
    persistent_term:erase(nova_dispatch),
    persistent_term:erase(nova_use_stacktrace),
    nova_test_helper:cleanup_nova_env(Prev).

%%====================================================================
%% execute/2 — callback path (normal controller)
%%====================================================================

execute_json_return_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        Callback = fun(_Req) -> {json, #{hello => world}} end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(200, maps:get(resp_status_code, Req1))
    end}.

execute_status_return_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        Callback = fun(_Req) -> {status, 404, #{}, <<"not found">>} end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(404, maps:get(resp_status_code, Req1))
    end}.

execute_redirect_return_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        Callback = fun(_Req) -> {redirect, <<"/login">>} end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(302, maps:get(resp_status_code, Req1))
    end}.

execute_controller_crash_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        Callback = fun(_Req) -> error(intentional_crash) end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(500, maps:get(resp_status_code, Req1))
    end}.

execute_controller_throw_status_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        Callback = fun(_Req) -> throw({403, forbidden}) end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(403, maps:get(resp_status_code, Req1))
    end}.

execute_controller_crash_with_stacktrace_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        persistent_term:put(nova_use_stacktrace, true),
        Callback = fun(_Req) -> error(boom) end,
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/">>),
        Env = #{callback => Callback, app => test_app, secure => false,
                controller_data => #{}},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(500, maps:get(resp_status_code, Req1)),
        CrashInfo = maps:get(crash_info, Req1),
        ?assert(maps:is_key(stacktrace, CrashInfo))
    end}.

%%====================================================================
%% execute/2 — cowboy_handler path
%%====================================================================

execute_cowboy_handler_ok_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        meck:new(test_cowboy_handler, [non_strict, no_link]),
        meck:expect(test_cowboy_handler, init, fun(Req, _Args) -> {ok, Req, #{}} end),
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/ws">>),
        Env = #{cowboy_handler => test_cowboy_handler, arguments => #{}, app => test_app},
        {ok, Req1, Env1} = nova_handler:execute(Req, Env),
        ?assertMatch(Req1 when is_map(Req1), Req1),
        ?assert(maps:is_key(result, Env1)),
        meck:unload(test_cowboy_handler)
    end}.

execute_cowboy_handler_crash_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        meck:new(test_cowboy_handler2, [non_strict, no_link]),
        meck:expect(test_cowboy_handler2, init, fun(_Req, _Args) -> error(crash) end),
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/ws">>),
        Env = #{cowboy_handler => test_cowboy_handler2, arguments => #{}, app => test_app},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(404, maps:get(resp_status_code, Req1)),
        meck:unload(test_cowboy_handler2)
    end}.

execute_cowboy_handler_crash_with_stacktrace_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        persistent_term:put(nova_use_stacktrace, true),
        meck:new(test_cowboy_handler3, [non_strict, no_link]),
        meck:expect(test_cowboy_handler3, init, fun(_Req, _Args) -> error(boom) end),
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/ws">>),
        Env = #{cowboy_handler => test_cowboy_handler3, arguments => #{}, app => test_app},
        {ok, Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assertEqual(404, maps:get(resp_status_code, Req1)),
        CrashInfo = maps:get(crash_info, Req1),
        ?assert(maps:is_key(stacktrace, CrashInfo)),
        meck:unload(test_cowboy_handler3)
    end}.

execute_cowboy_handler_upgrade_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        meck:new(test_upgrade_handler, [non_strict, no_link]),
        meck:expect(test_upgrade_handler, init, fun(Req, _Args) ->
            {test_upgrade_mod, Req, my_state}
        end),
        meck:new(test_upgrade_mod, [non_strict, no_link]),
        meck:expect(test_upgrade_mod, upgrade, fun(Req, Env, _Handler, _State) ->
            {ok, Req, Env}
        end),
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/ws">>),
        Env = #{cowboy_handler => test_upgrade_handler, arguments => #{}},
        {ok, _Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assert(meck:called(test_upgrade_mod, upgrade, '_')),
        meck:unload(test_upgrade_handler),
        meck:unload(test_upgrade_mod)
    end}.

execute_cowboy_handler_upgrade_with_opts_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        meck:new(test_upgrade_handler4, [non_strict, no_link]),
        meck:expect(test_upgrade_handler4, init, fun(Req, _Args) ->
            {test_upgrade_mod4, Req, my_state, my_opts}
        end),
        meck:new(test_upgrade_mod4, [non_strict, no_link]),
        meck:expect(test_upgrade_mod4, upgrade, fun(Req, Env, _Handler, _State, _Opts) ->
            {ok, Req, Env}
        end),
        Req = nova_test_helper:mock_req(<<"GET">>, <<"/ws">>),
        Env = #{cowboy_handler => test_upgrade_handler4, arguments => #{}},
        {ok, _Req1, _Env1} = nova_handler:execute(Req, Env),
        ?assert(meck:called(test_upgrade_mod4, upgrade, '_')),
        meck:unload(test_upgrade_handler4),
        meck:unload(test_upgrade_mod4)
    end}.

%%====================================================================
%% terminate/3
%%====================================================================

terminate_with_callback_test_() ->
    {setup, ?SETUP, ?CLEANUP, fun() ->
        ?assertEqual(ok, nova_handler:terminate(normal, #{}, fun erlang:date/0))
    end}.


Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/CaaS/Lamina

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.