total rebase
[anni] / test / pleroma / conversation / participation_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Conversation.ParticipationTest do
6   use Pleroma.DataCase, async: true
7   import Pleroma.Factory
8   alias Pleroma.Conversation
9   alias Pleroma.Conversation.Participation
10   alias Pleroma.Repo
11   alias Pleroma.User
12   alias Pleroma.Web.CommonAPI
13
14   test "getting a participation will also preload things" do
15     user = insert(:user)
16     other_user = insert(:user)
17
18     {:ok, _activity} =
19       CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
20
21     [participation] = Participation.for_user(user)
22
23     participation = Participation.get(participation.id, preload: [:conversation])
24
25     assert %Pleroma.Conversation{} = participation.conversation
26   end
27
28   test "for a new conversation or a reply, it doesn't mark the author's participation as unread" do
29     user = insert(:user)
30     other_user = insert(:user)
31
32     {:ok, _} =
33       CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
34
35     user = User.get_cached_by_id(user.id)
36     other_user = User.get_cached_by_id(other_user.id)
37
38     [%{read: true}] = Participation.for_user(user)
39     [%{read: false} = participation] = Participation.for_user(other_user)
40     assert Participation.unread_count(user) == 0
41     assert Participation.unread_count(other_user) == 1
42
43     {:ok, _} =
44       CommonAPI.post(other_user, %{
45         status: "Hey @#{user.nickname}.",
46         visibility: "direct",
47         in_reply_to_conversation_id: participation.id
48       })
49
50     user = User.get_cached_by_id(user.id)
51     other_user = User.get_cached_by_id(other_user.id)
52
53     [%{read: false}] = Participation.for_user(user)
54     [%{read: true}] = Participation.for_user(other_user)
55
56     assert Participation.unread_count(user) == 1
57     assert Participation.unread_count(other_user) == 0
58   end
59
60   test "for a new conversation, it sets the recipients of the participation" do
61     user = insert(:user)
62     other_user = insert(:user)
63     third_user = insert(:user)
64
65     {:ok, activity} =
66       CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
67
68     user = User.get_cached_by_id(user.id)
69     other_user = User.get_cached_by_id(other_user.id)
70     [participation] = Participation.for_user(user)
71     participation = Pleroma.Repo.preload(participation, :recipients)
72
73     assert length(participation.recipients) == 2
74     assert user in participation.recipients
75     assert other_user in participation.recipients
76
77     # Mentioning another user in the same conversation will not add a new recipients.
78
79     {:ok, _activity} =
80       CommonAPI.post(user, %{
81         in_reply_to_status_id: activity.id,
82         status: "Hey @#{third_user.nickname}.",
83         visibility: "direct"
84       })
85
86     [participation] = Participation.for_user(user)
87     participation = Pleroma.Repo.preload(participation, :recipients)
88
89     assert length(participation.recipients) == 2
90   end
91
92   test "it creates a participation for a conversation and a user" do
93     user = insert(:user)
94     conversation = insert(:conversation)
95
96     {:ok, %Participation{} = participation} =
97       Participation.create_for_user_and_conversation(user, conversation)
98
99     {:ok, participation} = time_travel(participation, -2)
100
101     assert participation.user_id == user.id
102     assert participation.conversation_id == conversation.id
103
104     # Creating again returns the same participation
105     {:ok, %Participation{} = participation_two} =
106       Participation.create_for_user_and_conversation(user, conversation)
107
108     assert participation.id == participation_two.id
109     refute participation.updated_at == participation_two.updated_at
110   end
111
112   test "recreating an existing participations sets it to unread" do
113     participation = insert(:participation, %{read: true})
114
115     {:ok, participation} =
116       Participation.create_for_user_and_conversation(
117         participation.user,
118         participation.conversation
119       )
120
121     refute participation.read
122   end
123
124   test "it marks a participation as read" do
125     participation = insert(:participation, %{updated_at: ~N[2017-07-17 17:09:58], read: false})
126     {:ok, updated_participation} = Participation.mark_as_read(participation)
127
128     assert updated_participation.read
129     assert :gt = NaiveDateTime.compare(updated_participation.updated_at, participation.updated_at)
130   end
131
132   test "it marks a participation as unread" do
133     participation = insert(:participation, %{read: true})
134     {:ok, participation} = Participation.mark_as_unread(participation)
135
136     refute participation.read
137   end
138
139   test "it marks all the user's participations as read" do
140     user = insert(:user)
141     other_user = insert(:user)
142     participation1 = insert(:participation, %{read: false, user: user})
143     participation2 = insert(:participation, %{read: false, user: user})
144     participation3 = insert(:participation, %{read: false, user: other_user})
145
146     {:ok, _, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)
147
148     assert Participation.get(participation1.id).read == true
149     assert Participation.get(participation2.id).read == true
150     assert Participation.get(participation3.id).read == false
151   end
152
153   test "gets all the participations for a user, ordered by updated at descending" do
154     user = insert(:user)
155     {:ok, activity_one} = CommonAPI.post(user, %{status: "x", visibility: "direct"})
156     {:ok, activity_two} = CommonAPI.post(user, %{status: "x", visibility: "direct"})
157
158     {:ok, activity_three} =
159       CommonAPI.post(user, %{
160         status: "x",
161         visibility: "direct",
162         in_reply_to_status_id: activity_one.id
163       })
164
165     # Offset participations because the accuracy of updated_at is down to a second
166
167     for {activity, offset} <- [{activity_two, 1}, {activity_three, 2}] do
168       conversation = Conversation.get_for_ap_id(activity.data["context"])
169       participation = Participation.for_user_and_conversation(user, conversation)
170       updated_at = NaiveDateTime.add(Map.get(participation, :updated_at), offset)
171
172       Ecto.Changeset.change(participation, %{updated_at: updated_at})
173       |> Repo.update!()
174     end
175
176     assert [participation_one, participation_two] = Participation.for_user(user)
177
178     object2 = Pleroma.Object.normalize(activity_two, fetch: false)
179     object3 = Pleroma.Object.normalize(activity_three, fetch: false)
180
181     user = Repo.get(Pleroma.User, user.id)
182
183     assert participation_one.conversation.ap_id == object3.data["context"]
184     assert participation_two.conversation.ap_id == object2.data["context"]
185     assert participation_one.conversation.users == [user]
186
187     # Pagination
188     assert [participation_one] = Participation.for_user(user, %{"limit" => 1})
189
190     assert participation_one.conversation.ap_id == object3.data["context"]
191
192     # With last_activity_id
193     assert [participation_one] =
194              Participation.for_user_with_last_activity_id(user, %{"limit" => 1})
195
196     assert participation_one.last_activity_id == activity_three.id
197   end
198
199   test "Doesn't die when the conversation gets empty" do
200     user = insert(:user)
201
202     {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
203     [participation] = Participation.for_user_with_last_activity_id(user)
204
205     assert participation.last_activity_id == activity.id
206
207     {:ok, _} = CommonAPI.delete(activity.id, user)
208
209     [] = Participation.for_user_with_last_activity_id(user)
210   end
211
212   test "it sets recipients, always keeping the owner of the participation even when not explicitly set" do
213     user = insert(:user)
214     other_user = insert(:user)
215
216     {:ok, _activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
217     [participation] = Participation.for_user_with_last_activity_id(user)
218
219     participation = Repo.preload(participation, :recipients)
220     user = User.get_cached_by_id(user.id)
221
222     assert participation.recipients |> length() == 1
223     assert user in participation.recipients
224
225     {:ok, participation} = Participation.set_recipients(participation, [other_user.id])
226
227     assert participation.recipients |> length() == 2
228     assert user in participation.recipients
229     assert other_user in participation.recipients
230   end
231
232   describe "blocking" do
233     test "when the user blocks a recipient, the existing conversations with them are marked as read" do
234       blocker = insert(:user)
235       blocked = insert(:user)
236       third_user = insert(:user)
237
238       {:ok, _direct1} =
239         CommonAPI.post(third_user, %{
240           status: "Hi @#{blocker.nickname}",
241           visibility: "direct"
242         })
243
244       {:ok, _direct2} =
245         CommonAPI.post(third_user, %{
246           status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
247           visibility: "direct"
248         })
249
250       {:ok, _direct3} =
251         CommonAPI.post(blocked, %{
252           status: "Hi @#{blocker.nickname}",
253           visibility: "direct"
254         })
255
256       {:ok, _direct4} =
257         CommonAPI.post(blocked, %{
258           status: "Hi @#{blocker.nickname}, @#{third_user.nickname}",
259           visibility: "direct"
260         })
261
262       assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
263                Participation.for_user(blocker)
264
265       assert Participation.unread_count(blocker) == 4
266
267       {:ok, _user_relationship} = User.block(blocker, blocked)
268
269       # The conversations with the blocked user are marked as read
270       assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
271                Participation.for_user(blocker)
272
273       assert Participation.unread_count(blocker) == 1
274
275       # The conversation is not marked as read for the blocked user
276       assert [_, _, %{read: false}] = Participation.for_user(blocked)
277       assert Participation.unread_count(blocker) == 1
278
279       # The conversation is not marked as read for the third user
280       assert [%{read: false}, _, _] = Participation.for_user(third_user)
281       assert Participation.unread_count(third_user) == 1
282     end
283
284     test "the new conversation with the blocked user is not marked as unread " do
285       blocker = insert(:user)
286       blocked = insert(:user)
287       third_user = insert(:user)
288
289       {:ok, _user_relationship} = User.block(blocker, blocked)
290
291       # When the blocked user is the author
292       {:ok, _direct1} =
293         CommonAPI.post(blocked, %{
294           status: "Hi @#{blocker.nickname}",
295           visibility: "direct"
296         })
297
298       assert [%{read: true}] = Participation.for_user(blocker)
299       assert Participation.unread_count(blocker) == 0
300
301       # When the blocked user is a recipient
302       {:ok, _direct2} =
303         CommonAPI.post(third_user, %{
304           status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
305           visibility: "direct"
306         })
307
308       assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
309       assert Participation.unread_count(blocker) == 0
310
311       assert [%{read: false}, _] = Participation.for_user(blocked)
312       assert Participation.unread_count(blocked) == 1
313     end
314
315     test "the conversation with the blocked user is not marked as unread on a reply" do
316       blocker = insert(:user)
317       blocked = insert(:user)
318       third_user = insert(:user)
319
320       {:ok, _direct1} =
321         CommonAPI.post(blocker, %{
322           status: "Hi @#{third_user.nickname}, @#{blocked.nickname}",
323           visibility: "direct"
324         })
325
326       {:ok, _user_relationship} = User.block(blocker, blocked)
327       assert [%{read: true}] = Participation.for_user(blocker)
328
329       assert Participation.unread_count(blocker) == 0
330       assert [blocked_participation] = Participation.for_user(blocked)
331
332       # When it's a reply from the blocked user
333       {:ok, _direct2} =
334         CommonAPI.post(blocked, %{
335           status: "reply",
336           visibility: "direct",
337           in_reply_to_conversation_id: blocked_participation.id
338         })
339
340       assert [%{read: true}] = Participation.for_user(blocker)
341
342       assert Participation.unread_count(blocker) == 0
343       assert [third_user_participation] = Participation.for_user(third_user)
344
345       # When it's a reply from the third user
346       {:ok, _direct3} =
347         CommonAPI.post(third_user, %{
348           status: "reply",
349           visibility: "direct",
350           in_reply_to_conversation_id: third_user_participation.id
351         })
352
353       assert [%{read: true}] = Participation.for_user(blocker)
354       assert Participation.unread_count(blocker) == 0
355
356       # Marked as unread for the blocked user
357       assert [%{read: false}] = Participation.for_user(blocked)
358
359       assert Participation.unread_count(blocked) == 1
360     end
361   end
362
363   test "deletes a conversation" do
364     user = insert(:user)
365     other_user = insert(:user)
366
367     {:ok, _activity} =
368       CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})
369
370     assert [participation] = Participation.for_user(other_user)
371     assert {:ok, _} = Participation.delete(participation)
372     assert [] == Participation.for_user(other_user)
373   end
374 end