diff --git a/.gitignore b/.gitignore index 2726ae3571ae774a2fd5e6e45dab716ac0d35699..727e3341c94de3d886c601a123c4e66c4f83fa98 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,12 @@ priv/protobuf/service_room/jobType.proto priv/protobuf/service_room/messageEvent.proto priv/protobuf/service_room/messageStatus.proto priv/protobuf/service_room/messageType.proto +apps/roster/priv/macbert/Model/FakeNumbers.swift +apps/roster/priv/macbert/Model/Presence.swift +apps/roster/priv/macbert/Model/cx.swift +apps/roster/priv/macbert/Model/load.swift +apps/roster/priv/macbert/Spec/FakeNumbers_Spec.swift +apps/roster/priv/macbert/Spec/Presence_Spec.swift +apps/roster/priv/macbert/Spec/cx_Spec.swift +apps/roster/priv/macbert/Spec/load_Spec.swift +apps/service/priv/ diff --git a/apps/roster/src/protocol/roster_link.erl b/apps/roster/src/protocol/roster_link.erl index 74be0db6f81a1f65ddfe76acb742ad56bdd4cb5c..87f933d546ac3967d6850864b2b1e922714d7610 100644 --- a/apps/roster/src/protocol/roster_link.erl +++ b/apps/roster/src/protocol/roster_link.erl @@ -4,38 +4,30 @@ -include_lib("roster/include/static/roster_var.hrl"). -include_lib("roster/include/static/main_text.hrl"). -include_lib("n2o/include/n2o.hrl"). + +-import(lists, [map/2, member/2, keydelete/3, keymember/3, keystore/4, keyreplace/4]). + -compile(export_all). +% ====================================== +% API +% ====================================== info(#'Link'{id = LinkId, type = group, status = join = LStatus} = RequestData, Req, #cx{params = ClientId} = State) when LinkId /= [] -> roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), -Res = case get_room_by_link_Id(LinkId) of - #ok{code = #'Room'{id = RoomId} = Room}-> - #'Member'{id = MemberId, reader = MemberReader} = - Member = case roster:muc_member(ClientId, RoomId) of - #'Member'{} = M -> M; - _ -> - PhoneID = roster:phone_id(ClientId), - Member0 = #'Member'{ - feed_id = #muc{name = RoomId}, - phone_id = PhoneID, - reader = 0, update = 0, - presence = roster:is_online(PhoneID), - status = member}, - #ok{code = Member1} = roster:to_member(PhoneID), - roster:patch_member(Member0, Member1) - end, - Messages = case roster:member_lastmsg(Member) of - {error, _} -> []; - {_, LM} -> LM - end, + Res = case get_room_by_link_id(LinkId) of + #ok{code = #'Room'{id = RoomId} = Room} -> + #'Member'{id = MemberId, reader = MemberReader} = + Member = build_membership_card(ClientId, RoomId), + Messages = member_last_messages(Member), {UnreadMsg, LastMsg, _} = roster:unread_msg({'Message', Messages}, MemberReader, MemberId), {#'Member'{}, [], #'Room'{} = Room1} = roster:add_member(Room, Member, {no_muc_message, Room#'Room'.readers}), {As, Ms} = roster:split_members(roster:members(#muc{name = Room1#'Room'.id}, presence)), - roster:reader_cache(Room1#'Room'{status = join, last_msg = LastMsg, unread = UnreadMsg, members = Ms, admins = As}); - #error{code = not_found} = Err -> Err - end, + #'Room'{} = Room2 = roster:reader_cache(Room1#'Room'{status = join, last_msg = LastMsg, unread = UnreadMsg, members = Ms, admins = As}), + ok_code(Room2); + #error{code = not_found} = Err -> Err + end, roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; @@ -43,12 +35,13 @@ info(#'Link'{id = LinkId, name = RoomId, type = group, status = update = LStatus Req, #cx{params = ClientId} = State) when RoomId /= [], LinkId /= [] -> roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), Res = case roster:muc_member(ClientId, RoomId) of - #'Member'{status = admin} = M-> - update_link(RoomId, RequestData); - #'Member'{status = member} -> - error_code(permission_denied); - _-> - error_code(invalide_data) + #'Member'{status = Status} when + Status == owner; Status == admin -> + update_link(RoomId, RequestData); + #'Member'{status = member} -> + error_code(permission_denied); + _ -> + error_code(invalid_data) end, roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; @@ -56,71 +49,130 @@ info(#'Link'{id = LinkId, name = RoomId, type = group, status = update = LStatus info(#'Link'{id = LinkId, type = group, status = get = LStatus} = RequestData, Req, #cx{params = ClientId} = State) when LinkId /= [] -> roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), - Res = case get_room_by_link_Id(LinkId) of - #ok{code = #'Room'{id = RoomId} = Room } -> #ok{code = Room#'Room'{status = info}}; - #error{code = not_found} = Err -> Err - end, + Res = case get_room_by_link_id(LinkId) of + #ok{code = #'Room'{} = Room} -> + #ok{code = Room#'Room'{status = info}}; + #error{code = not_found} = Err -> Err + end, roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; -info(#'Link'{status = delete = LStatus} = RequestData, Req, #cx{params = ClientId} = State) -> +info(#'Link'{type = group, status = delete = LStatus} = RequestData, + Req, #cx{params = ClientId} = State) -> roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + % Res = delete_link(ClientId, RequestData), Res = error_code(permission_denied), roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; info(#'Link'{} = RequestData, Req, #cx{params = ClientId} = State) -> roster:info(?MODULE, "~p:Link/unknown:~p", [ClientId, RequestData]), - {reply, {bert, io_error_code(invalid_data)}, Req, State}. + {reply, {bert, io_error(invalid_data)}, Req, State}. +% ====================================== +% Helper functions +% ====================================== -gen_link(RoomId)-> +gen_link(RoomId) -> TmpLinkId = hash(RoomId), LinkId = case kvs:get('Link', TmpLinkId) of - #ok{code = #'Link'{id = TmpLinkId}}-> gen_link(RoomId); - #error{code = not_found} -> TmpLinkId - end, - -#'Link'{ + #ok{code = #'Link'{id = TmpLinkId}} -> gen_link(RoomId); + #error{code = not_found} -> TmpLinkId + end, + + #'Link'{ % id = <>, id = LinkId, name = RoomId, - type = group + type = group, + status = get }. -update_link(RoomId, #'Link'{id = LinkId} = Link) -> - Res = case kvs:get('Room', RoomId) of +update_link(#'Link'{name = RoomId} = Link) -> + update_link(RoomId, Link). +update_link(RoomId, #'Link'{id = LinkId}) -> + case kvs:get('Room', RoomId) of #ok{code = #'Room'{links = Links0} = Room} -> - NewLink = gen_link(RoomId), - % Links1 = lists:keyreplace(LinkId, #'Link'.id, Links0, NewLink), - ok = kvs:put(Room#'Room'{links = [NewLink]}), - ok = kvs:delete('Link', LinkId), - ok = kvs:put(NewLink), - NewLink; - #error{code = not_found} = Err -> Err - end, - Res. + % remove case statement for multiple links + case keymember(LinkId, #'Link'.id, Links0) of + true -> + NewLink = gen_link(RoomId), + Links1 = keystore(LinkId, #'Link'.id, Links0, NewLink), + ok = kvs:put(Room#'Room'{links = Links1}), + ok = kvs:delete('Link', LinkId), + ok = kvs:put(NewLink), + ok_code(NewLink); + false -> #error{code = invalid_data} + end; + #error{code = not_found} = Err -> Err + end. + +delete_link(ClientId, #'Link'{id = LinkId, name = RoomId}) -> + map( + fun(#'Member'{status = member}) -> + error_code(permission_denied); + (#'Member'{status = Status}) when + Status == owner; Status == admin -> + case kvs:get('Room', RoomId) of + #ok{code = #'Room'{id = RoomId, links = Links} = Room} -> + ok = kvs:put(Room#'Room'{links = keydelete(LinkId, #'Link'.id, Links)}), + ok = kvs:delete('Link', LinkId); + #error{code = not_found} = Err -> Err + end; + (_) -> error_code(invalid_data) + end, roster:muc_member(ClientId, RoomId)). -purge_link(RoomId)-> - [kvs:delete('Link', Lid) || #'Link'{id = Lid,name = RoomId}<- kvs:all('Link')], +purge_room_links(RoomId) -> + %% TODO: Improve error handling + case kvs:get('Room', RoomId) of + #ok{code = #'Room'{links = Links}} when + is_list(Links), Links /= [] -> + [kvs:delete('Link', Lid) || #'Link'{id = Lid, name = RoomId} <- Links]; + _ -> [] + end, ok. hash(Data) -> {_, <>} = roster:gen_token([], Data), Hash. -get_room_by_link_Id(<>)-> - get_room_by_link_Id(LinkId); -get_room_by_link_Id(Id) -> - case kvs:get('Link',Id) of - #ok{code = #'Link'{name = RoomId}}-> - case kvs:get('Room',RoomId) of + +get_room_by_link_id(<>) -> + get_room_by_link_id(LinkId); +get_room_by_link_id(Id) -> + case kvs:get('Link', Id) of + #ok{code = #'Link'{name = RoomId}} -> + case kvs:get('Room', RoomId) of #ok{code = #'Room'{}} = Ok -> Ok; #error{code = not_found} = Err -> Err end; #error{code = not_found} = Err -> Err - end. -io_error_code(Payload) -> io_code(error_code(Payload)). -io_ok_code(Payload) -> io_code(ok_code(Payload)). + end. + +build_membership_card(ClientId, RoomId) -> + case roster:muc_member(ClientId, RoomId) of + #'Member'{} = M -> M; + _ -> + PhoneID = roster:phone_id(ClientId), + Member0 = #'Member'{ + feed_id = #muc{name = RoomId}, + phone_id = PhoneID, + reader = 0, update = 0, + presence = roster:is_online(PhoneID), + status = member}, + #ok{code = Member1} = roster:to_member(PhoneID), + roster:patch_member(Member0, Member1) + end. + +member_last_messages(Member) -> + case roster:member_lastmsg(Member) of + {error, _} -> []; + {_, LM} -> LM + end. + +ok_code(Payload) -> #ok{code = Payload}. +error_code(Payload) -> #error{code = Payload}. + +io_ok(Payload) -> io_code(ok_code(Payload)). +io_error(Payload) -> io_code(error_code(Payload)). + io_code(Payload) -> #io{code = Payload}. -error_code(Payload) -> #error{code = Payload}. -ok_code(Payload) -> #ok{code =Payload}. diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index 8efcefd88d27c9a2ef80da24988b140d81875471..46f60f057c3be55cc6d4a2030a9aaac841ace937 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -779,7 +779,7 @@ validate(Payload, ClientId) -> %% MUC API purge_room(false) -> []; purge_room(Room) -> - roster_link:purge_link(Room), + roster_link:purge_room_links(Room), [begin kvs:remove('Member', Id), kvs:delete(reader, R), unsubscribe_room(M), remove_rooms(PhoneId, [Room]) end || #'Member'{id = Id, phone_id = PhoneId, reader = R} = M <- members(#muc{name = Room})], kvs:delete(writer, {muc, Room}), delete_msg(#muc{name = Room}), diff --git a/apps/roster/src/test/room_test.erl b/apps/roster/src/test/room_test.erl index 0b5a3d8dbf2d6df6ea6fdd35d9f3f601125dffbb..96f1a2de5f2ff7dba447fa4e3be1bf215bf2a9da 100644 --- a/apps/roster/src/test/room_test.erl +++ b/apps/roster/src/test/room_test.erl @@ -125,15 +125,12 @@ link() -> roster_client:send_receive(AClientId, Counter + 1, #'Room'{status = add, id = RoomID, admins = [EM]}), true = is_integer(U), %% Get the room link by RoomId - #ok{code = #'Room'{links = [#'Link'{id = LinkId}]}} = kvs:get('Room', Room), + #ok{code = #'Room'{links = [#'Link'{id = LinkId}]}} = kvs:get('Room', Room), %% Assert linkId by Link get request - #ok{code = #'Room'{links = [#'Link'{id = LinkId}], readers = [_ | _]}} = -roster_client:send_receive(AClientId, 1, #'Link'{type = group, status = get, id = LinkId}), + #ok{code = #'Room'{links = [#'Link'{id = LinkId}], readers = [_ | _]}} = + roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, type = group, status = get}), %% Update the link from the administrator and compare it with the old one. - % #'Room'{links = LinkId} =/= - X = roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = Room, type = group, status = update}), -io:format("~nXXXXXXXXXXXXXXXXXXXXX~p~n",[X]), - + #ok{code = #'Link'{id = LinkId}} /= roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = Room, type = group, status = update}), %%add member with message size roster:purge_user(FPhone = <<"6789000">>), {FClientId, FToken} = roster_client:reg_fake_user(FPhone), @@ -171,59 +168,102 @@ reg_by_phone(Phones, #'muc'{} = Feed) -> test_joinlink() -> +%%% Initializing test state. RoomName = RoomId = <<"test_room_joinlink">>, roster:purge_room(RoomId), Feed = #muc{name = RoomId}, - GroupAMembers = [{_APhone = <<"9000000001">>, admin}, - {_BPhone = <<"9000000002">>, member}], - GroupACounter = length(GroupAMembers), - [{APhoneId, AClientId, _, _}, {BPhoneId, BClientId, _, _}] = - GroupAPCs = reg_by_phone(GroupAMembers, Feed), - GroupAClientIds = [ClientId || {_, ClientId, _, _} <- GroupAPCs], - roster_client:set_filer(GroupAClientIds, filter), - {[Owner | _], Members} = roster:split_members([M || {_, _, M, _} <- GroupAPCs]), - RoomInit = #'Room'{status = create, type = group, id = RoomId, name = RoomName, - admins = [Owner#'Member'{alias = <<"Radostin">>}], members = [hd(Members)]}, - #'Room'{id = RoomId, links = [#'Link'{id = LinkId}], - admins = [_RoomOwner = #'Member'{phone_id = APhoneId}], - members = [#'Member'{phone_id = BPhoneId}]} = - roster_client:send_receive(AClientId, GroupACounter, RoomInit), - -%% Test: get room info with fake room link - roster_link:io_error_code(room_not_found) == roster_client:send_receive(BClientId, 1, #'Link'{id = ?BLANK_LINK, type = group, status = get}), -%% Test: get room info with correct room link - kvs:get('Room', RoomId) == #ok{code = roster_client:send_receive(BClientId, 1, #'Link'{id = LinkId, type = group, status = get})}, - GroupBMembers = [{<<"9000000003">>, member}, {<<"9000000004">>, member}], -% GroupBCounter = length(GroupBMembers), - [{CPhoneId, CClientId, _, _}, {_, DClientId, _, _}] = - _GroupBPCs = reg_by_phone(GroupBMembers, Feed), - -%% Test: join room - #'Room'{members = Room2Members} = roster_client:send_receive(CClientId, 1, #'Link'{id = LinkId, type = group, status = join}), - true == lists:keymember(CPhoneId, #'Member'.phone_id, Room2Members), -%% Test: update old link and try to join with new one - % #'Link'{name = LinkId1} = -roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = update}). -% , -% roster_client:send_receive(DClientId, 1, #'Link'{id = LinkId1, type = group, status = join}), -% %%% Test: Delete joinlink -% %% Non admin user -% roster_link:io_error_code(invalid_data) == roster_client:send_receive(DClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = delete}), -% %% Admin user, unavailable link (old) -% roster_link:io_error_code(invalid_data) == -% roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = delete}), -% %% Test join: unexisting link -% [{_, EClientId, _, _}] = reg_by_phone([{<<"9000000005">>, member}], #muc{}), -% roster_link:io_error_code(room_not_found) == roster_client:send_receive(EClientId, 1, #'Link'{id = LinkId1, type = group, status = join}), -% %% Disconnect users -% [roster_client:stop_client(CId) || CId <- GroupAClientIds ++ [CClientId, DClientId, EClientId]], -% ok. + Phones = [ + {_APhone = <<"9000000001">>, admin}, + {_BPhone = <<"9000000002">>, admin}, + {_CPhone = <<"9000000003">>, member}, + {_DPhone = <<"9000000004">>, member}, + {_EPhone = <<"9000000005">>, member} + ], + + PCMT = [ + {_APhoneId, AClientId, _AMember, _AToken}, + {_BPhoneId, BClientId, _BMember, _BToken}, + {CPhoneId, _CClientId, _CMember, _CToken}, + {DPhoneId, DClientId, _DMember, _DToken}, + {EPhoneId, EClientId, _EMember, _EToken} + ] = reg_by_phone(Phones, Feed), + + { + _PhoneIds, + ClientIds, + MembersList, + _Tokens + } = lists:foldr(fun({P, C, M, T}, {Ps, Cs, Ms, Ts}) -> + {[P | Ps], [C | Cs], [M | Ms], [T | Ts]} end, {[], [], [], []}, PCMT), + roster_client:set_filer(ClientIds, filter), + + {[Owner, Admin], RoomMembers} = roster:split_members(MembersList), + +%%% Initializing Room state. + RoomInit = #'Room'{id = RoomId, name = RoomName, + members = [hd(RoomMembers)], + admins = [ + Owner#'Member'{alias = <<"Radostin">>, status = admin}, + Admin#'Member'{alias = <<"Admin">>, status = admin} + ], + type = group, + status = create}, + +%%% Create and join first three clients to the 'Room'. + #'Room'{id = RoomId, links = [#'Link'{id = LinkId} = Link], + admins = RoomAdmins0, + members = [#'Member'{phone_id = CPhoneId}]} = + roster_client:send_receive(AClientId, 3, RoomInit), + lists:all(fun(true)-> true end, [lists:keymember(CId, #'Member'.phone_id, RoomAdmins0) + || CId <- [Owner#'Member'.phone_id, Admin#'Member'.phone_id]]), + +%%% Start tests: +%% Test: try to get room info; fake room link; admin + {error, not_found} = roster_client:send_receive(BClientId, 1, Link#'Link'{id = ?BLANK_LINK, status = get}), + +%% Test: try to get room info; correct room link; admin + {ok, #'Room'{id = RoomId, links = [#'Link'{id = LinkId}], status = info}} = + roster_client:send_receive(BClientId, 1, Link#'Link'{status = get}), + +%% Test: try to get room info; correct room link; not room member + {ok, #'Room'{id = RoomId, links = [#'Link'{id = LinkId}], status = info}} = + roster_client:send_receive(DClientId, 1, Link#'Link'{status = get}), + +%% Test: join room + {ok, #'Room'{id = RoomId, status = join} = Room1} = + roster_client:send_receive(DClientId, 1, Link#'Link'{status = join}), +% Check if new member is added + true = lists:keymember(DPhoneId, #'Member'.phone_id, Room1#'Room'.members), +%% Test: try to update room link; room member, non admin + {error, permission_denied} = roster_client:send_receive(DClientId, 1, Link#'Link'{status = update}), +%% Test: try to update room link; non room member, non admin, fake link + {error, invalid_data} = roster_client:send_receive(EClientId, 1, Link#'Link'{id = ?BLANK_LINK, status = update}), +%% Test: try to update room link; non room member + {error, invalid_data} = roster_client:send_receive(EClientId, 1, Link#'Link'{status = update}), +%% Test: try to update room link; room member, admin + {ok, #'Link'{name = RoomId} = Link1} = roster_client:send_receive(BClientId, 1, Link#'Link'{status = update}), +%% Test: try to get room info; correct room link; + {ok, #'Room'{id = RoomId, links = [Link1], status = info}} = + roster_client:send_receive(EClientId, 1, Link1#'Link'{status = get}), +%% Test: try to update room link; non-existent link, room member, admin + {error, invalid_data} = roster_client:send_receive(BClientId, 1, Link#'Link'{status = update}), +%% Test: join room; non-existent link, non room member + {error, not_found} = roster_client:send_receive(EClientId, 1, Link#'Link'{status = join}), +%% Test: join room; non room member + {ok, #'Room'{id = RoomId, links = [Link1], status = join} = Room2} = + roster_client:send_receive(EClientId, 1, Link1#'Link'{status = join}), +% Check if new member is added + true = lists:keymember(EPhoneId, #'Member'.phone_id, Room2#'Room'.members), + %%% Disconnect Clients + [roster_client:stop_client(CId) || CId <- ClientIds], + ok. suite() -> [ check_alias_increment(), - add_many_members() + add_many_members(), + test_joinlink() ]. check() -> roster:check(?MODULE). \ No newline at end of file