aboutsummaryrefslogtreecommitdiff
path: root/server/test/jchat_prop_SUITE.erl
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
committerCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
commit49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch)
tree61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/test/jchat_prop_SUITE.erl
i vibe coded itHEADmaster
Diffstat (limited to 'server/test/jchat_prop_SUITE.erl')
-rw-r--r--server/test/jchat_prop_SUITE.erl131
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.