diff options
Diffstat (limited to 'server/test/jchat_prop_SUITE.erl')
-rw-r--r-- | server/test/jchat_prop_SUITE.erl | 131 |
1 files changed, 131 insertions, 0 deletions
diff --git a/server/test/jchat_prop_SUITE.erl b/server/test/jchat_prop_SUITE.erl new file mode 100644 index 0000000..c47e531 --- /dev/null +++ b/server/test/jchat_prop_SUITE.erl @@ -0,0 +1,131 @@ +-module(jchat_prop_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("proper/include/proper.hrl"). +-include("../src/jchat.hrl"). + +%% CT callbacks +-export([all/0, init_per_suite/1, end_per_suite/1]). + +%% Test cases +-export([prop_id_validation/1, + prop_conversation_crud/1, + prop_message_crud/1, + prop_json_encoding/1]). + +all() -> + [prop_id_validation, + prop_conversation_crud, + prop_message_crud, + prop_json_encoding]. + +init_per_suite(Config) -> + application:ensure_all_started(jchat), + timer:sleep(1000), + Config. + +end_per_suite(_Config) -> + application:stop(jchat), + ok. + +%% Property tests + +prop_id_validation(_Config) -> + ?assert(proper:quickcheck(?FORALL(Id, valid_id(), + jchat_utils:validate_id(Id) =:= true))), + + ?assert(proper:quickcheck(?FORALL(Id, invalid_id(), + jchat_utils:validate_id(Id) =:= false))). + +prop_conversation_crud(_Config) -> + ?assert(proper:quickcheck(?FORALL({Id, Attrs}, {valid_id(), conversation_attrs()}, + begin + % Create + {ok, Conv} = jchat_db:create_conversation(Id, Attrs), + % Get + {ok, Conv2} = jchat_db:get_conversation(Id), + % Update + Updates = #{title => <<"Updated Title">>}, + {ok, Conv3} = jchat_db:update_conversation(Id, Updates), + % Verify + Conv#conversation.id =:= Conv2#conversation.id andalso + Conv3#conversation.title =:= <<"Updated Title">> + end))). + +prop_message_crud(_Config) -> + ?assert(proper:quickcheck(?FORALL({ConvId, MsgId, Attrs}, + {valid_id(), valid_id(), message_attrs()}, + begin + % Create conversation first + ConvAttrs = #{title => <<"Test">>, participant_ids => [<<"user1">>]}, + {ok, _} = jchat_db:create_conversation(ConvId, ConvAttrs), + + % Create message + MsgAttrs = Attrs#{conversation_id => ConvId, sender_id => <<"user1">>}, + {ok, Msg} = jchat_db:create_message(MsgId, MsgAttrs), + + % Get message + {ok, Msg2} = jchat_db:get_message(MsgId), + + % Verify + Msg#message.id =:= Msg2#message.id + end))). + +prop_json_encoding(_Config) -> + ?assert(proper:quickcheck(?FORALL(Data, json_data(), + begin + JSON = jchat_utils:json_encode(Data), + {ok, Decoded} = jchat_utils:json_decode(JSON), + normalize_json(Data) =:= normalize_json(Decoded) + end))). + +%% Generators + +valid_id() -> + ?LET(Chars, non_empty(list(oneof([ + choose($A, $Z), + choose($a, $z), + choose($0, $9), + return($-), + return($_) + ]))), list_to_binary(Chars)). + +invalid_id() -> + oneof([ + <<>>, % Empty + ?LET(N, choose(256, 1000), list_to_binary(lists:duplicate(N, $a))), % Too long + <<"invalid=chars">>, % Invalid characters + <<"spaces not allowed">> % Spaces + ]). + +conversation_attrs() -> + ?LET({Title, Desc, Archived, Muted, Participants}, + {binary(), oneof([binary(), null]), boolean(), boolean(), list(valid_id())}, + #{title => Title, + description => Desc, + is_archived => Archived, + is_muted => Muted, + participant_ids => Participants}). + +message_attrs() -> + ?LET({Body, BodyType}, {binary(), oneof([<<"text/plain">>, <<"text/html">>])}, + #{body => Body, body_type => BodyType}). + +json_data() -> + ?LAZY(oneof([ + binary(), + integer(), + boolean(), + null, + list(json_data()), + map(binary(), json_data()) + ])). + +%% Helpers + +normalize_json(null) -> null; +normalize_json(Data) when is_map(Data) -> + maps:map(fun(_, V) -> normalize_json(V) end, Data); +normalize_json(Data) when is_list(Data) -> + [normalize_json(Item) || Item <- Data]; +normalize_json(Data) -> Data. |