Resolve follow activity from accept/reject without ID (#328)
[akkoma] / test / pleroma / web / activity_pub / utils_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.UtilsTest do
6 use Pleroma.DataCase, async: true
7 alias Pleroma.Activity
8 alias Pleroma.Object
9 alias Pleroma.Repo
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.Utils
12 alias Pleroma.Web.AdminAPI.AccountView
13 alias Pleroma.Web.CommonAPI
14
15 import Pleroma.Factory
16
17 require Pleroma.Constants
18
19 describe "fetch the latest Follow" do
20 test "fetches the latest Follow activity" do
21 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
22 follower = User.get_cached_by_ap_id(activity.data["actor"])
23 followed = User.get_cached_by_ap_id(activity.data["object"])
24
25 assert activity == Utils.fetch_latest_follow(follower, followed)
26 end
27 end
28
29 describe "determine_explicit_mentions()" do
30 test "works with an object that has mentions" do
31 object = %{
32 "tag" => [
33 %{
34 "type" => "Mention",
35 "href" => "https://example.com/~alyssa",
36 "name" => "Alyssa P. Hacker"
37 }
38 ]
39 }
40
41 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
42 end
43
44 test "works with an object that does not have mentions" do
45 object = %{
46 "tag" => [
47 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
48 ]
49 }
50
51 assert Utils.determine_explicit_mentions(object) == []
52 end
53
54 test "works with an object that has mentions and other tags" do
55 object = %{
56 "tag" => [
57 %{
58 "type" => "Mention",
59 "href" => "https://example.com/~alyssa",
60 "name" => "Alyssa P. Hacker"
61 },
62 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
63 ]
64 }
65
66 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
67 end
68
69 test "works with an object that has no tags" do
70 object = %{}
71
72 assert Utils.determine_explicit_mentions(object) == []
73 end
74
75 test "works with an object that has only IR tags" do
76 object = %{"tag" => ["2hu"]}
77
78 assert Utils.determine_explicit_mentions(object) == []
79 end
80
81 test "works with an object has tags as map" do
82 object = %{
83 "tag" => %{
84 "type" => "Mention",
85 "href" => "https://example.com/~alyssa",
86 "name" => "Alyssa P. Hacker"
87 }
88 }
89
90 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
91 end
92 end
93
94 describe "make_like_data" do
95 setup do
96 user = insert(:user)
97 other_user = insert(:user)
98 third_user = insert(:user)
99 [user: user, other_user: other_user, third_user: third_user]
100 end
101
102 test "addresses actor's follower address if the activity is public", %{
103 user: user,
104 other_user: other_user,
105 third_user: third_user
106 } do
107 expected_to = Enum.sort([user.ap_id, other_user.follower_address])
108 expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id])
109
110 {:ok, activity} =
111 CommonAPI.post(user, %{
112 status:
113 "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?"
114 })
115
116 %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
117 assert Enum.sort(to) == expected_to
118 assert Enum.sort(cc) == expected_cc
119 end
120
121 test "does not adress actor's follower address if the activity is not public", %{
122 user: user,
123 other_user: other_user,
124 third_user: third_user
125 } do
126 expected_to = Enum.sort([user.ap_id])
127 expected_cc = [third_user.ap_id]
128
129 {:ok, activity} =
130 CommonAPI.post(user, %{
131 status: "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!",
132 visibility: "private"
133 })
134
135 %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
136 assert Enum.sort(to) == expected_to
137 assert Enum.sort(cc) == expected_cc
138 end
139 end
140
141 test "make_json_ld_header/0" do
142 assert Utils.make_json_ld_header() == %{
143 "@context" => [
144 "https://www.w3.org/ns/activitystreams",
145 "http://localhost:4001/schemas/litepub-0.1.jsonld",
146 %{
147 "@language" => "und"
148 }
149 ]
150 }
151 end
152
153 describe "get_existing_votes" do
154 test "fetches existing votes" do
155 user = insert(:user)
156 other_user = insert(:user)
157
158 {:ok, activity} =
159 CommonAPI.post(user, %{
160 status: "How do I pronounce LaTeX?",
161 poll: %{
162 options: ["laytekh", "lahtekh", "latex"],
163 expires_in: 20,
164 multiple: true
165 }
166 })
167
168 object = Object.normalize(activity, fetch: false)
169 {:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1])
170 assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes)
171 end
172
173 test "fetches only Create activities" do
174 user = insert(:user)
175 other_user = insert(:user)
176
177 {:ok, activity} =
178 CommonAPI.post(user, %{
179 status: "Are we living in a society?",
180 poll: %{
181 options: ["yes", "no"],
182 expires_in: 20
183 }
184 })
185
186 object = Object.normalize(activity, fetch: false)
187 {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
188 {:ok, _activity} = CommonAPI.favorite(user, activity.id)
189 [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
190 assert fetched_vote.id == vote.id
191 end
192 end
193
194 describe "update_follow_state_for_all/2" do
195 test "updates the state of all Follow activities with the same actor and object" do
196 user = insert(:user, is_locked: true)
197 follower = insert(:user)
198
199 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
200 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
201
202 data =
203 follow_activity_two.data
204 |> Map.put("state", "accept")
205
206 cng = Ecto.Changeset.change(follow_activity_two, data: data)
207
208 {:ok, follow_activity_two} = Repo.update(cng)
209
210 {:ok, follow_activity_two} =
211 Utils.update_follow_state_for_all(follow_activity_two, "accept")
212
213 assert refresh_record(follow_activity).data["state"] == "accept"
214 assert refresh_record(follow_activity_two).data["state"] == "accept"
215 end
216
217 test "also updates the state of accepted follows" do
218 user = insert(:user)
219 follower = insert(:user)
220
221 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
222 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
223
224 {:ok, follow_activity_two} =
225 Utils.update_follow_state_for_all(follow_activity_two, "reject")
226
227 assert refresh_record(follow_activity).data["state"] == "reject"
228 assert refresh_record(follow_activity_two).data["state"] == "reject"
229 end
230 end
231
232 describe "update_element_in_object/3" do
233 test "updates likes" do
234 user = insert(:user)
235 activity = insert(:note_activity)
236 object = Object.normalize(activity, fetch: false)
237
238 assert {:ok, updated_object} =
239 Utils.update_element_in_object(
240 "like",
241 [user.ap_id],
242 object
243 )
244
245 assert updated_object.data["likes"] == [user.ap_id]
246 assert updated_object.data["like_count"] == 1
247 end
248 end
249
250 describe "add_like_to_object/2" do
251 test "add actor to likes" do
252 user = insert(:user)
253 user2 = insert(:user)
254 object = insert(:note)
255
256 assert {:ok, updated_object} =
257 Utils.add_like_to_object(
258 %Activity{data: %{"actor" => user.ap_id}},
259 object
260 )
261
262 assert updated_object.data["likes"] == [user.ap_id]
263 assert updated_object.data["like_count"] == 1
264
265 assert {:ok, updated_object2} =
266 Utils.add_like_to_object(
267 %Activity{data: %{"actor" => user2.ap_id}},
268 updated_object
269 )
270
271 assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id]
272 assert updated_object2.data["like_count"] == 2
273 end
274 end
275
276 describe "remove_like_from_object/2" do
277 test "removes ap_id from likes" do
278 user = insert(:user)
279 user2 = insert(:user)
280 object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2})
281
282 assert {:ok, updated_object} =
283 Utils.remove_like_from_object(
284 %Activity{data: %{"actor" => user.ap_id}},
285 object
286 )
287
288 assert updated_object.data["likes"] == [user2.ap_id]
289 assert updated_object.data["like_count"] == 1
290 end
291 end
292
293 describe "get_existing_like/2" do
294 test "fetches existing like" do
295 note_activity = insert(:note_activity)
296 assert object = Object.normalize(note_activity, fetch: false)
297
298 user = insert(:user)
299 refute Utils.get_existing_like(user.ap_id, object)
300 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
301
302 assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
303 end
304 end
305
306 describe "get_get_existing_announce/2" do
307 test "returns nil if announce not found" do
308 actor = insert(:user)
309 refute Utils.get_existing_announce(actor.ap_id, %{data: %{"id" => "test"}})
310 end
311
312 test "fetches existing announce" do
313 note_activity = insert(:note_activity)
314 assert object = Object.normalize(note_activity, fetch: false)
315 actor = insert(:user)
316
317 {:ok, announce} = CommonAPI.repeat(note_activity.id, actor)
318 assert Utils.get_existing_announce(actor.ap_id, object) == announce
319 end
320 end
321
322 describe "fetch_latest_block/2" do
323 test "fetches last block activities" do
324 user1 = insert(:user)
325 user2 = insert(:user)
326
327 assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
328 assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
329 assert {:ok, %Activity{} = activity} = CommonAPI.block(user1, user2)
330
331 assert Utils.fetch_latest_block(user1, user2) == activity
332 end
333 end
334
335 describe "recipient_in_message/3" do
336 test "returns true when recipient in `to`" do
337 recipient = insert(:user)
338 actor = insert(:user)
339 assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id})
340
341 assert Utils.recipient_in_message(
342 recipient,
343 actor,
344 %{"to" => [recipient.ap_id], "cc" => ""}
345 )
346 end
347
348 test "returns true when recipient in `cc`" do
349 recipient = insert(:user)
350 actor = insert(:user)
351 assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id})
352
353 assert Utils.recipient_in_message(
354 recipient,
355 actor,
356 %{"cc" => [recipient.ap_id], "to" => ""}
357 )
358 end
359
360 test "returns true when recipient in `bto`" do
361 recipient = insert(:user)
362 actor = insert(:user)
363 assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id})
364
365 assert Utils.recipient_in_message(
366 recipient,
367 actor,
368 %{"bcc" => "", "bto" => [recipient.ap_id]}
369 )
370 end
371
372 test "returns true when recipient in `bcc`" do
373 recipient = insert(:user)
374 actor = insert(:user)
375 assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id})
376
377 assert Utils.recipient_in_message(
378 recipient,
379 actor,
380 %{"bto" => "", "bcc" => [recipient.ap_id]}
381 )
382 end
383
384 test "returns true when message without addresses fields" do
385 recipient = insert(:user)
386 actor = insert(:user)
387 assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id})
388
389 assert Utils.recipient_in_message(
390 recipient,
391 actor,
392 %{"btod" => "", "bccc" => [recipient.ap_id]}
393 )
394 end
395
396 test "returns false" do
397 recipient = insert(:user)
398 actor = insert(:user)
399 refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"})
400 end
401 end
402
403 describe "lazy_put_activity_defaults/2" do
404 test "returns map with id and published data" do
405 note_activity = insert(:note_activity)
406 object = Object.normalize(note_activity, fetch: false)
407 res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]})
408 assert res["context"] == object.data["id"]
409 assert res["id"]
410 assert res["published"]
411 end
412
413 test "returns map with fake id and published data" do
414 assert %{
415 "context" => "pleroma:fakecontext",
416 "id" => "pleroma:fakeid",
417 "published" => _
418 } = Utils.lazy_put_activity_defaults(%{}, true)
419 end
420
421 test "returns activity data with object" do
422 note_activity = insert(:note_activity)
423 object = Object.normalize(note_activity, fetch: false)
424
425 res =
426 Utils.lazy_put_activity_defaults(%{
427 "context" => object.data["id"],
428 "object" => %{}
429 })
430
431 assert res["context"] == object.data["id"]
432 assert res["id"]
433 assert res["published"]
434 assert res["object"]["id"]
435 assert res["object"]["published"]
436 assert res["object"]["context"] == object.data["id"]
437 end
438 end
439
440 describe "make_flag_data" do
441 test "returns empty map when params is invalid" do
442 assert Utils.make_flag_data(%{}, %{}) == %{}
443 end
444
445 test "returns map with Flag object" do
446 reporter = insert(:user)
447 target_account = insert(:user)
448 {:ok, activity} = CommonAPI.post(target_account, %{status: "foobar"})
449 context = Utils.generate_context_id()
450 content = "foobar"
451
452 target_ap_id = target_account.ap_id
453 activity_ap_id = activity.data["id"]
454
455 res =
456 Utils.make_flag_data(
457 %{
458 actor: reporter,
459 context: context,
460 account: target_account,
461 statuses: [%{"id" => activity.data["id"]}],
462 content: content
463 },
464 %{}
465 )
466
467 note_obj = %{
468 "type" => "Note",
469 "id" => activity_ap_id,
470 "content" => content,
471 "published" => activity.object.data["published"],
472 "actor" =>
473 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
474 }
475
476 assert %{
477 "type" => "Flag",
478 "content" => ^content,
479 "context" => ^context,
480 "object" => [^target_ap_id, ^note_obj],
481 "state" => "open"
482 } = res
483 end
484 end
485
486 describe "add_announce_to_object/2" do
487 test "adds actor to announcement" do
488 user = insert(:user)
489 object = insert(:note)
490
491 activity =
492 insert(:note_activity,
493 data: %{
494 "actor" => user.ap_id,
495 "cc" => [Pleroma.Constants.as_public()]
496 }
497 )
498
499 assert {:ok, updated_object} = Utils.add_announce_to_object(activity, object)
500 assert updated_object.data["announcements"] == [user.ap_id]
501 assert updated_object.data["announcement_count"] == 1
502 end
503 end
504
505 describe "remove_announce_from_object/2" do
506 test "removes actor from announcements" do
507 user = insert(:user)
508 user2 = insert(:user)
509
510 object =
511 insert(:note,
512 data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2}
513 )
514
515 activity = insert(:note_activity, data: %{"actor" => user.ap_id})
516
517 assert {:ok, updated_object} = Utils.remove_announce_from_object(activity, object)
518 assert updated_object.data["announcements"] == [user2.ap_id]
519 assert updated_object.data["announcement_count"] == 1
520 end
521 end
522
523 describe "get_cached_emoji_reactions/1" do
524 test "returns the data or an emtpy list" do
525 object = insert(:note)
526 assert Utils.get_cached_emoji_reactions(object) == []
527
528 object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]})
529 assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]]
530
531 object = insert(:note, data: %{"reactions" => %{}})
532 assert Utils.get_cached_emoji_reactions(object) == []
533 end
534 end
535 end