-module(jchat_http_SUITE). -compile(export_all). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). %%==================================================================== %% CT Callbacks %%==================================================================== suite() -> [{timetrap, {seconds, 30}}]. init_per_suite(Config) -> % Start the application application:ensure_all_started(jchat), % Wait for server to start timer:sleep(1000), [{server_url, "http://localhost:8081"} | Config]. end_per_suite(_Config) -> application:stop(jchat), ok. init_per_testcase(_TestCase, Config) -> % Clean up test data mnesia:clear_table(user), Config. end_per_testcase(_TestCase, _Config) -> ok. all() -> [test_auth_register_endpoint, test_auth_login_endpoint, test_auth_me_endpoint, test_jmap_api_with_auth, test_jmap_api_without_auth, test_cors_headers]. %%==================================================================== %% Test Cases %%==================================================================== test_auth_register_endpoint(Config) -> ServerUrl = ?config(server_url, Config), % Prepare registration data Email = "test@example.com", Password = "testpass123", DisplayName = "Test User", ReqBody = jsx:encode(#{ <<"email">> => list_to_binary(Email), <<"password">> => list_to_binary(Password), <<"displayName">> => list_to_binary(DisplayName) }), % Make registration request Url = ServerUrl ++ "/auth/register", Headers = [{"content-type", "application/json"}], {ok, {{_Version, 201, _ReasonPhrase}, _Headers, ResponseBody}} = httpc:request(post, {Url, Headers, "application/json", ReqBody}, [], []), % Parse response ResponseMap = jsx:decode(list_to_binary(ResponseBody)), ?assert(maps:is_key(<<"token">>, ResponseMap)), ?assert(maps:is_key(<<"user">>, ResponseMap)), User = maps:get(<<"user">>, ResponseMap), ?assertEqual(list_to_binary(Email), maps:get(<<"email">>, User)), ?assertEqual(list_to_binary(DisplayName), maps:get(<<"displayName">>, User)). test_auth_login_endpoint(Config) -> ServerUrl = ?config(server_url, Config), % First register a user Email = "login.test@example.com", Password = "logintest123", DisplayName = "Login Test User", {ok, _User} = jchat_auth:register_user( list_to_binary(Email), list_to_binary(Password), list_to_binary(DisplayName) ), % Now test login ReqBody = jsx:encode(#{ <<"email">> => list_to_binary(Email), <<"password">> => list_to_binary(Password) }), Url = ServerUrl ++ "/auth/login", Headers = [{"content-type", "application/json"}], {ok, {{_Version, 200, _ReasonPhrase}, _Headers, ResponseBody}} = httpc:request(post, {Url, Headers, "application/json", ReqBody}, [], []), % Parse response ResponseMap = jsx:decode(list_to_binary(ResponseBody)), ?assert(maps:is_key(<<"token">>, ResponseMap)), ?assert(maps:is_key(<<"user">>, ResponseMap)). test_auth_me_endpoint(Config) -> ServerUrl = ?config(server_url, Config), % Register and login to get token Email = "me.test@example.com", Password = "metest123", DisplayName = "Me Test User", {ok, _User} = jchat_auth:register_user( list_to_binary(Email), list_to_binary(Password), list_to_binary(DisplayName) ), {ok, {_AuthUser, Token}} = jchat_auth:authenticate_user( list_to_binary(Email), list_to_binary(Password) ), % Test /auth/me endpoint Url = ServerUrl ++ "/auth/me", Headers = [{"authorization", "Bearer " ++ binary_to_list(Token)}], {ok, {{_Version, 200, _ReasonPhrase}, _Headers, ResponseBody}} = httpc:request(get, {Url, Headers}, [], []), % Parse response ResponseMap = jsx:decode(list_to_binary(ResponseBody)), ?assert(maps:is_key(<<"user">>, ResponseMap)), User = maps:get(<<"user">>, ResponseMap), ?assertEqual(list_to_binary(Email), maps:get(<<"email">>, User)). test_jmap_api_with_auth(Config) -> ServerUrl = ?config(server_url, Config), % Register and login to get token Email = "jmap.test@example.com", Password = "jmaptest123", DisplayName = "JMAP Test User", {ok, _User} = jchat_auth:register_user( list_to_binary(Email), list_to_binary(Password), list_to_binary(DisplayName) ), {ok, {_AuthUser, Token}} = jchat_auth:authenticate_user( list_to_binary(Email), list_to_binary(Password) ), % Test JMAP API call ReqBody = jsx:encode(#{ <<"using">> => [<<"urn:ietf:params:jmap:core">>, <<"https://jmap.io/jchat/">>], <<"methodCalls">> => [ [<<"Conversation/query">>, #{ <<"accountId">> => <<"default">>, <<"filter">> => #{}, <<"sort">> => [#{<<"property">> => <<"lastMessageAt">>, <<"isAscending">> => false}] }, <<"c1">>] ] }), Url = ServerUrl ++ "/jmap/api", Headers = [ {"content-type", "application/json"}, {"authorization", "Bearer " ++ binary_to_list(Token)} ], {ok, {{_Version, 200, _ReasonPhrase}, _Headers, ResponseBody}} = httpc:request(post, {Url, Headers, "application/json", ReqBody}, [], []), % Parse response ResponseMap = jsx:decode(list_to_binary(ResponseBody)), ?assert(maps:is_key(<<"methodResponses">>, ResponseMap)). test_jmap_api_without_auth(Config) -> ServerUrl = ?config(server_url, Config), % Test JMAP API call without authentication ReqBody = jsx:encode(#{ <<"using">> => [<<"urn:ietf:params:jmap:core">>, <<"https://jmap.io/jchat/">>], <<"methodCalls">> => [ [<<"Conversation/query">>, #{ <<"accountId">> => <<"default">>, <<"filter">> => #{}, <<"sort">> => [#{<<"property">> => <<"lastMessageAt">>, <<"isAscending">> => false}] }, <<"c1">>] ] }), Url = ServerUrl ++ "/jmap/api", Headers = [{"content-type", "application/json"}], {ok, {{_Version, 401, _ReasonPhrase}, _Headers, _ResponseBody}} = httpc:request(post, {Url, Headers, "application/json", ReqBody}, [], []). test_cors_headers(Config) -> ServerUrl = ?config(server_url, Config), % Test CORS preflight Url = ServerUrl ++ "/auth/register", Headers = [ {"origin", "http://localhost:3000"}, {"access-control-request-method", "POST"}, {"access-control-request-headers", "content-type,authorization"} ], {ok, {{_Version, StatusCode, _ReasonPhrase}, ResponseHeaders, _ResponseBody}} = httpc:request(options, {Url, Headers}, [], []), % Should return 200 or 204 for OPTIONS ?assert(StatusCode =:= 200 orelse StatusCode =:= 204), % Check for CORS headers HeadersMap = maps:from_list(ResponseHeaders), ?assert(maps:is_key("access-control-allow-origin", HeadersMap) orelse maps:is_key("Access-Control-Allow-Origin", HeadersMap)).