summaryrefslogtreecommitdiff
path: root/test/pleroma/conversation/participation_test.exs
blob: a25e17c957f1206b8b510cc75e67184d0b14535d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Conversation.ParticipationTest do
  use Pleroma.DataCase, async: true
  import Pleroma.Factory
  alias Pleroma.Conversation
  alias Pleroma.Conversation.Participation
  alias Pleroma.Repo
  alias Pleroma.User
  alias Pleroma.Web.CommonAPI

  test "getting a participation will also preload things" do
    user = insert(:user)
    other_user = insert(:user)

    {:ok, _activity} =
      CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})

    [participation] = Participation.for_user(user)

    participation = Participation.get(participation.id, preload: [:conversation])

    assert %Pleroma.Conversation{} = participation.conversation
  end

  test "for a new conversation or a reply, it doesn't mark the author's participation as unread" do
    user = insert(:user)
    other_user = insert(:user)

    {:ok, _} =
      CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})

    user = User.get_cached_by_id(user.id)
    other_user = User.get_cached_by_id(other_user.id)

    [%{read: true}] = Participation.for_user(user)
    [%{read: false} = participation] = Participation.for_user(other_user)
    assert Participation.unread_count(user) == 0
    assert Participation.unread_count(other_user) == 1

    {:ok, _} =
      CommonAPI.post(other_user, %{
        status: "Hey @#{user.nickname}.",
        visibility: "direct",
        in_reply_to_conversation_id: participation.id
      })

    user = User.get_cached_by_id(user.id)
    other_user = User.get_cached_by_id(other_user.id)

    [%{read: false}] = Participation.for_user(user)
    [%{read: true}] = Participation.for_user(other_user)

    assert Participation.unread_count(user) == 1
    assert Participation.unread_count(other_user) == 0
  end

  test "for a new conversation, it sets the recipents of the participation" do
    user = insert(:user)
    other_user = insert(:user)
    third_user = insert(:user)

    {:ok, activity} =
      CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})

    user = User.get_cached_by_id(user.id)
    other_user = User.get_cached_by_id(other_user.id)
    [participation] = Participation.for_user(user)
    participation = Pleroma.Repo.preload(participation, :recipients)

    assert length(participation.recipients) == 2
    assert user in participation.recipients
    assert other_user in participation.recipients

    # Mentioning another user in the same conversation will not add a new recipients.

    {:ok, _activity} =
      CommonAPI.post(user, %{
        in_reply_to_status_id: activity.id,
        status: "Hey @#{third_user.nickname}.",
        visibility: "direct"
      })

    [participation] = Participation.for_user(user)
    participation = Pleroma.Repo.preload(participation, :recipients)

    assert length(participation.recipients) == 2
  end

  test "it creates a participation for a conversation and a user" do
    user = insert(:user)
    conversation = insert(:conversation)

    {:ok, %Participation{} = participation} =
      Participation.create_for_user_and_conversation(user, conversation)

    {:ok, participation} = time_travel(participation, -2)

    assert participation.user_id == user.id
    assert participation.conversation_id == conversation.id

    # Creating again returns the same participation
    {:ok, %Participation{} = participation_two} =
      Participation.create_for_user_and_conversation(user, conversation)

    assert participation.id == participation_two.id
    refute participation.updated_at == participation_two.updated_at
  end

  test "recreating an existing participations sets it to unread" do
    participation = insert(:participation, %{read: true})

    {:ok, participation} =
      Participation.create_for_user_and_conversation(
        participation.user,
        participation.conversation
      )

    refute participation.read
  end

  test "it marks a participation as read" do
    participation = insert(:participation, %{read: false})
    {:ok, updated_participation} = Participation.mark_as_read(participation)

    assert updated_participation.read
    assert updated_participation.updated_at == participation.updated_at
  end

  test "it marks a participation as unread" do
    participation = insert(:participation, %{read: true})
    {:ok, participation} = Participation.mark_as_unread(participation)

    refute participation.read
  end

  test "it marks all the user's participations as read" do
    user = insert(:user)
    other_user = insert(:user)
    participation1 = insert(:participation, %{read: false, user: user})
    participation2 = insert(:participation, %{read: false, user: user})
    participation3 = insert(:participation, %{read: false, user: other_user})

    {:ok, _, [%{read: true}, %{read: true}]} = Participation.mark_all_as_read(user)

    assert Participation.get(participation1.id).read == true
    assert Participation.get(participation2.id).read == true
    assert Participation.get(participation3.id).read == false
  end

  test "gets all the participations for a user, ordered by updated at descending" do
    user = insert(:user)
    {:ok, activity_one} = CommonAPI.post(user, %{status: "x", visibility: "direct"})
    {:ok, activity_two} = CommonAPI.post(user, %{status: "x", visibility: "direct"})

    {:ok, activity_three} =
      CommonAPI.post(user, %{
        status: "x",
        visibility: "direct",
        in_reply_to_status_id: activity_one.id
      })

    # Offset participations because the accuracy of updated_at is down to a second

    for {activity, offset} <- [{activity_two, 1}, {activity_three, 2}] do
      conversation = Conversation.get_for_ap_id(activity.data["context"])
      participation = Participation.for_user_and_conversation(user, conversation)
      updated_at = NaiveDateTime.add(Map.get(participation, :updated_at), offset)

      Ecto.Changeset.change(participation, %{updated_at: updated_at})
      |> Repo.update!()
    end

    assert [participation_one, participation_two] = Participation.for_user(user)

    object2 = Pleroma.Object.normalize(activity_two, fetch: false)
    object3 = Pleroma.Object.normalize(activity_three, fetch: false)

    user = Repo.get(Pleroma.User, user.id)

    assert participation_one.conversation.ap_id == object3.data["context"]
    assert participation_two.conversation.ap_id == object2.data["context"]
    assert participation_one.conversation.users == [user]

    # Pagination
    assert [participation_one] = Participation.for_user(user, %{"limit" => 1})

    assert participation_one.conversation.ap_id == object3.data["context"]

    # With last_activity_id
    assert [participation_one] =
             Participation.for_user_with_last_activity_id(user, %{"limit" => 1})

    assert participation_one.last_activity_id == activity_three.id
  end

  test "Doesn't die when the conversation gets empty" do
    user = insert(:user)

    {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
    [participation] = Participation.for_user_with_last_activity_id(user)

    assert participation.last_activity_id == activity.id

    {:ok, _} = CommonAPI.delete(activity.id, user)

    [] = Participation.for_user_with_last_activity_id(user)
  end

  test "it sets recipients, always keeping the owner of the participation even when not explicitly set" do
    user = insert(:user)
    other_user = insert(:user)

    {:ok, _activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
    [participation] = Participation.for_user_with_last_activity_id(user)

    participation = Repo.preload(participation, :recipients)
    user = User.get_cached_by_id(user.id)

    assert participation.recipients |> length() == 1
    assert user in participation.recipients

    {:ok, participation} = Participation.set_recipients(participation, [other_user.id])

    assert participation.recipients |> length() == 2
    assert user in participation.recipients
    assert other_user in participation.recipients
  end

  describe "blocking" do
    test "when the user blocks a recipient, the existing conversations with them are marked as read" do
      blocker = insert(:user)
      blocked = insert(:user)
      third_user = insert(:user)

      {:ok, _direct1} =
        CommonAPI.post(third_user, %{
          status: "Hi @#{blocker.nickname}",
          visibility: "direct"
        })

      {:ok, _direct2} =
        CommonAPI.post(third_user, %{
          status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
          visibility: "direct"
        })

      {:ok, _direct3} =
        CommonAPI.post(blocked, %{
          status: "Hi @#{blocker.nickname}",
          visibility: "direct"
        })

      {:ok, _direct4} =
        CommonAPI.post(blocked, %{
          status: "Hi @#{blocker.nickname}, @#{third_user.nickname}",
          visibility: "direct"
        })

      assert [%{read: false}, %{read: false}, %{read: false}, %{read: false}] =
               Participation.for_user(blocker)

      assert Participation.unread_count(blocker) == 4

      {:ok, _user_relationship} = User.block(blocker, blocked)

      # The conversations with the blocked user are marked as read
      assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
               Participation.for_user(blocker)

      assert Participation.unread_count(blocker) == 1

      # The conversation is not marked as read for the blocked user
      assert [_, _, %{read: false}] = Participation.for_user(blocked)
      assert Participation.unread_count(blocker) == 1

      # The conversation is not marked as read for the third user
      assert [%{read: false}, _, _] = Participation.for_user(third_user)
      assert Participation.unread_count(third_user) == 1
    end

    test "the new conversation with the blocked user is not marked as unread " do
      blocker = insert(:user)
      blocked = insert(:user)
      third_user = insert(:user)

      {:ok, _user_relationship} = User.block(blocker, blocked)

      # When the blocked user is the author
      {:ok, _direct1} =
        CommonAPI.post(blocked, %{
          status: "Hi @#{blocker.nickname}",
          visibility: "direct"
        })

      assert [%{read: true}] = Participation.for_user(blocker)
      assert Participation.unread_count(blocker) == 0

      # When the blocked user is a recipient
      {:ok, _direct2} =
        CommonAPI.post(third_user, %{
          status: "Hi @#{blocker.nickname}, @#{blocked.nickname}",
          visibility: "direct"
        })

      assert [%{read: true}, %{read: true}] = Participation.for_user(blocker)
      assert Participation.unread_count(blocker) == 0

      assert [%{read: false}, _] = Participation.for_user(blocked)
      assert Participation.unread_count(blocked) == 1
    end

    test "the conversation with the blocked user is not marked as unread on a reply" do
      blocker = insert(:user)
      blocked = insert(:user)
      third_user = insert(:user)

      {:ok, _direct1} =
        CommonAPI.post(blocker, %{
          status: "Hi @#{third_user.nickname}, @#{blocked.nickname}",
          visibility: "direct"
        })

      {:ok, _user_relationship} = User.block(blocker, blocked)
      assert [%{read: true}] = Participation.for_user(blocker)

      assert Participation.unread_count(blocker) == 0
      assert [blocked_participation] = Participation.for_user(blocked)

      # When it's a reply from the blocked user
      {:ok, _direct2} =
        CommonAPI.post(blocked, %{
          status: "reply",
          visibility: "direct",
          in_reply_to_conversation_id: blocked_participation.id
        })

      assert [%{read: true}] = Participation.for_user(blocker)

      assert Participation.unread_count(blocker) == 0
      assert [third_user_participation] = Participation.for_user(third_user)

      # When it's a reply from the third user
      {:ok, _direct3} =
        CommonAPI.post(third_user, %{
          status: "reply",
          visibility: "direct",
          in_reply_to_conversation_id: third_user_participation.id
        })

      assert [%{read: true}] = Participation.for_user(blocker)
      assert Participation.unread_count(blocker) == 0

      # Marked as unread for the blocked user
      assert [%{read: false}] = Participation.for_user(blocked)

      assert Participation.unread_count(blocked) == 1
    end
  end

  test "deletes a conversation" do
    user = insert(:user)
    other_user = insert(:user)

    {:ok, _activity} =
      CommonAPI.post(user, %{status: "Hey @#{other_user.nickname}.", visibility: "direct"})

    assert [participation] = Participation.for_user(other_user)
    assert {:ok, _} = Participation.delete(participation)
    assert [] == Participation.for_user(other_user)
  end
end