Merge branch 'mix-emoji-task-pack-json' into 'develop'
[akkoma] / test / web / activity_pub / utils_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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
7 alias Pleroma.Activity
8 alias Pleroma.Object
9 alias Pleroma.Repo
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
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 "fetch the latest Block" do
30 test "fetches the latest Block activity" do
31 blocker = insert(:user)
32 blocked = insert(:user)
33 {:ok, activity} = ActivityPub.block(blocker, blocked)
34
35 assert activity == Utils.fetch_latest_block(blocker, blocked)
36 end
37 end
38
39 describe "determine_explicit_mentions()" do
40 test "works with an object that has mentions" do
41 object = %{
42 "tag" => [
43 %{
44 "type" => "Mention",
45 "href" => "https://example.com/~alyssa",
46 "name" => "Alyssa P. Hacker"
47 }
48 ]
49 }
50
51 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
52 end
53
54 test "works with an object that does not have mentions" do
55 object = %{
56 "tag" => [
57 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
58 ]
59 }
60
61 assert Utils.determine_explicit_mentions(object) == []
62 end
63
64 test "works with an object that has mentions and other tags" do
65 object = %{
66 "tag" => [
67 %{
68 "type" => "Mention",
69 "href" => "https://example.com/~alyssa",
70 "name" => "Alyssa P. Hacker"
71 },
72 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
73 ]
74 }
75
76 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
77 end
78
79 test "works with an object that has no tags" do
80 object = %{}
81
82 assert Utils.determine_explicit_mentions(object) == []
83 end
84
85 test "works with an object that has only IR tags" do
86 object = %{"tag" => ["2hu"]}
87
88 assert Utils.determine_explicit_mentions(object) == []
89 end
90
91 test "works with an object has tags as map" do
92 object = %{
93 "tag" => %{
94 "type" => "Mention",
95 "href" => "https://example.com/~alyssa",
96 "name" => "Alyssa P. Hacker"
97 }
98 }
99
100 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
101 end
102 end
103
104 describe "make_unlike_data/3" do
105 test "returns data for unlike activity" do
106 user = insert(:user)
107 like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"})
108
109 object = Object.normalize(like_activity.data["object"])
110
111 assert Utils.make_unlike_data(user, like_activity, nil) == %{
112 "type" => "Undo",
113 "actor" => user.ap_id,
114 "object" => like_activity.data,
115 "to" => [user.follower_address, object.data["actor"]],
116 "cc" => [Pleroma.Constants.as_public()],
117 "context" => like_activity.data["context"]
118 }
119
120 assert Utils.make_unlike_data(user, like_activity, "9mJEZK0tky1w2xD2vY") == %{
121 "type" => "Undo",
122 "actor" => user.ap_id,
123 "object" => like_activity.data,
124 "to" => [user.follower_address, object.data["actor"]],
125 "cc" => [Pleroma.Constants.as_public()],
126 "context" => like_activity.data["context"],
127 "id" => "9mJEZK0tky1w2xD2vY"
128 }
129 end
130 end
131
132 describe "make_like_data" do
133 setup do
134 user = insert(:user)
135 other_user = insert(:user)
136 third_user = insert(:user)
137 [user: user, other_user: other_user, third_user: third_user]
138 end
139
140 test "addresses actor's follower address if the activity is public", %{
141 user: user,
142 other_user: other_user,
143 third_user: third_user
144 } do
145 expected_to = Enum.sort([user.ap_id, other_user.follower_address])
146 expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id])
147
148 {:ok, activity} =
149 CommonAPI.post(user, %{
150 "status" =>
151 "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?"
152 })
153
154 %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
155 assert Enum.sort(to) == expected_to
156 assert Enum.sort(cc) == expected_cc
157 end
158
159 test "does not adress actor's follower address if the activity is not public", %{
160 user: user,
161 other_user: other_user,
162 third_user: third_user
163 } do
164 expected_to = Enum.sort([user.ap_id])
165 expected_cc = [third_user.ap_id]
166
167 {:ok, activity} =
168 CommonAPI.post(user, %{
169 "status" => "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!",
170 "visibility" => "private"
171 })
172
173 %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
174 assert Enum.sort(to) == expected_to
175 assert Enum.sort(cc) == expected_cc
176 end
177 end
178
179 describe "fetch_ordered_collection" do
180 import Tesla.Mock
181
182 test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do
183 mock(fn
184 %{method: :get, url: "http://mastodon.com/outbox"} ->
185 json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"})
186
187 %{method: :get, url: "http://mastodon.com/outbox?page=true"} ->
188 json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]})
189 end)
190
191 assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"]
192 end
193
194 test "fetches several pages in the right order one after another, but only the specified amount" do
195 mock(fn
196 %{method: :get, url: "http://example.com/outbox"} ->
197 json(%{
198 "type" => "OrderedCollectionPage",
199 "orderedItems" => [0],
200 "next" => "http://example.com/outbox?page=1"
201 })
202
203 %{method: :get, url: "http://example.com/outbox?page=1"} ->
204 json(%{
205 "type" => "OrderedCollectionPage",
206 "orderedItems" => [1],
207 "next" => "http://example.com/outbox?page=2"
208 })
209
210 %{method: :get, url: "http://example.com/outbox?page=2"} ->
211 json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]})
212 end)
213
214 assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0]
215 assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1]
216 end
217
218 test "returns an error if the url doesn't have an OrderedCollection/Page" do
219 mock(fn
220 %{method: :get, url: "http://example.com/not-an-outbox"} ->
221 json(%{"type" => "NotAnOutbox"})
222 end)
223
224 assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1)
225 end
226
227 test "returns the what was collected if there are less pages than specified" do
228 mock(fn
229 %{method: :get, url: "http://example.com/outbox"} ->
230 json(%{
231 "type" => "OrderedCollectionPage",
232 "orderedItems" => [0],
233 "next" => "http://example.com/outbox?page=1"
234 })
235
236 %{method: :get, url: "http://example.com/outbox?page=1"} ->
237 json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]})
238 end)
239
240 assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
241 end
242 end
243
244 test "make_json_ld_header/0" do
245 assert Utils.make_json_ld_header() == %{
246 "@context" => [
247 "https://www.w3.org/ns/activitystreams",
248 "http://localhost:4001/schemas/litepub-0.1.jsonld",
249 %{
250 "@language" => "und"
251 }
252 ]
253 }
254 end
255
256 describe "get_existing_votes" do
257 test "fetches existing votes" do
258 user = insert(:user)
259 other_user = insert(:user)
260
261 {:ok, activity} =
262 CommonAPI.post(user, %{
263 "status" => "How do I pronounce LaTeX?",
264 "poll" => %{
265 "options" => ["laytekh", "lahtekh", "latex"],
266 "expires_in" => 20,
267 "multiple" => true
268 }
269 })
270
271 object = Object.normalize(activity)
272 {:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1])
273 assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes)
274 end
275
276 test "fetches only Create activities" do
277 user = insert(:user)
278 other_user = insert(:user)
279
280 {:ok, activity} =
281 CommonAPI.post(user, %{
282 "status" => "Are we living in a society?",
283 "poll" => %{
284 "options" => ["yes", "no"],
285 "expires_in" => 20
286 }
287 })
288
289 object = Object.normalize(activity)
290 {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
291 vote_object = Object.normalize(vote)
292 {:ok, _activity, _object} = ActivityPub.like(user, vote_object)
293 [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
294 assert fetched_vote.id == vote.id
295 end
296 end
297
298 describe "update_follow_state_for_all/2" do
299 test "updates the state of all Follow activities with the same actor and object" do
300 user = insert(:user, info: %{locked: true})
301 follower = insert(:user)
302
303 {:ok, follow_activity} = ActivityPub.follow(follower, user)
304 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
305
306 data =
307 follow_activity_two.data
308 |> Map.put("state", "accept")
309
310 cng = Ecto.Changeset.change(follow_activity_two, data: data)
311
312 {:ok, follow_activity_two} = Repo.update(cng)
313
314 {:ok, follow_activity_two} =
315 Utils.update_follow_state_for_all(follow_activity_two, "accept")
316
317 assert refresh_record(follow_activity).data["state"] == "accept"
318 assert refresh_record(follow_activity_two).data["state"] == "accept"
319 end
320 end
321
322 describe "update_follow_state/2" do
323 test "updates the state of the given follow activity" do
324 user = insert(:user, info: %{locked: true})
325 follower = insert(:user)
326
327 {:ok, follow_activity} = ActivityPub.follow(follower, user)
328 {:ok, follow_activity_two} = ActivityPub.follow(follower, user)
329
330 data =
331 follow_activity_two.data
332 |> Map.put("state", "accept")
333
334 cng = Ecto.Changeset.change(follow_activity_two, data: data)
335
336 {:ok, follow_activity_two} = Repo.update(cng)
337
338 {:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject")
339
340 assert refresh_record(follow_activity).data["state"] == "pending"
341 assert refresh_record(follow_activity_two).data["state"] == "reject"
342 end
343 end
344
345 describe "update_element_in_object/3" do
346 test "updates likes" do
347 user = insert(:user)
348 activity = insert(:note_activity)
349 object = Object.normalize(activity)
350
351 assert {:ok, updated_object} =
352 Utils.update_element_in_object(
353 "like",
354 [user.ap_id],
355 object
356 )
357
358 assert updated_object.data["likes"] == [user.ap_id]
359 assert updated_object.data["like_count"] == 1
360 end
361 end
362
363 describe "add_like_to_object/2" do
364 test "add actor to likes" do
365 user = insert(:user)
366 user2 = insert(:user)
367 object = insert(:note)
368
369 assert {:ok, updated_object} =
370 Utils.add_like_to_object(
371 %Activity{data: %{"actor" => user.ap_id}},
372 object
373 )
374
375 assert updated_object.data["likes"] == [user.ap_id]
376 assert updated_object.data["like_count"] == 1
377
378 assert {:ok, updated_object2} =
379 Utils.add_like_to_object(
380 %Activity{data: %{"actor" => user2.ap_id}},
381 updated_object
382 )
383
384 assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id]
385 assert updated_object2.data["like_count"] == 2
386 end
387 end
388
389 describe "remove_like_from_object/2" do
390 test "removes ap_id from likes" do
391 user = insert(:user)
392 user2 = insert(:user)
393 object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2})
394
395 assert {:ok, updated_object} =
396 Utils.remove_like_from_object(
397 %Activity{data: %{"actor" => user.ap_id}},
398 object
399 )
400
401 assert updated_object.data["likes"] == [user2.ap_id]
402 assert updated_object.data["like_count"] == 1
403 end
404 end
405
406 describe "get_existing_like/2" do
407 test "fetches existing like" do
408 note_activity = insert(:note_activity)
409 assert object = Object.normalize(note_activity)
410
411 user = insert(:user)
412 refute Utils.get_existing_like(user.ap_id, object)
413 {:ok, like_activity, _object} = ActivityPub.like(user, object)
414
415 assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
416 end
417 end
418
419 describe "get_get_existing_announce/2" do
420 test "returns nil if announce not found" do
421 actor = insert(:user)
422 refute Utils.get_existing_announce(actor.ap_id, %{data: %{"id" => "test"}})
423 end
424
425 test "fetches existing announce" do
426 note_activity = insert(:note_activity)
427 assert object = Object.normalize(note_activity)
428 actor = insert(:user)
429
430 {:ok, announce, _object} = ActivityPub.announce(actor, object)
431 assert Utils.get_existing_announce(actor.ap_id, object) == announce
432 end
433 end
434
435 describe "fetch_latest_block/2" do
436 test "fetches last block activities" do
437 user1 = insert(:user)
438 user2 = insert(:user)
439
440 assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
441 assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2)
442 assert {:ok, %Activity{} = activity} = ActivityPub.block(user1, user2)
443
444 assert Utils.fetch_latest_block(user1, user2) == activity
445 end
446 end
447
448 describe "recipient_in_message/3" do
449 test "returns true when recipient in `to`" do
450 recipient = insert(:user)
451 actor = insert(:user)
452 assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id})
453
454 assert Utils.recipient_in_message(
455 recipient,
456 actor,
457 %{"to" => [recipient.ap_id], "cc" => ""}
458 )
459 end
460
461 test "returns true when recipient in `cc`" do
462 recipient = insert(:user)
463 actor = insert(:user)
464 assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id})
465
466 assert Utils.recipient_in_message(
467 recipient,
468 actor,
469 %{"cc" => [recipient.ap_id], "to" => ""}
470 )
471 end
472
473 test "returns true when recipient in `bto`" do
474 recipient = insert(:user)
475 actor = insert(:user)
476 assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id})
477
478 assert Utils.recipient_in_message(
479 recipient,
480 actor,
481 %{"bcc" => "", "bto" => [recipient.ap_id]}
482 )
483 end
484
485 test "returns true when recipient in `bcc`" do
486 recipient = insert(:user)
487 actor = insert(:user)
488 assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id})
489
490 assert Utils.recipient_in_message(
491 recipient,
492 actor,
493 %{"bto" => "", "bcc" => [recipient.ap_id]}
494 )
495 end
496
497 test "returns true when message without addresses fields" do
498 recipient = insert(:user)
499 actor = insert(:user)
500 assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id})
501
502 assert Utils.recipient_in_message(
503 recipient,
504 actor,
505 %{"btod" => "", "bccc" => [recipient.ap_id]}
506 )
507 end
508
509 test "returns false" do
510 recipient = insert(:user)
511 actor = insert(:user)
512 refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"})
513 end
514 end
515
516 describe "lazy_put_activity_defaults/2" do
517 test "returns map with id and published data" do
518 note_activity = insert(:note_activity)
519 object = Object.normalize(note_activity)
520 res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]})
521 assert res["context"] == object.data["id"]
522 assert res["context_id"] == object.id
523 assert res["id"]
524 assert res["published"]
525 end
526
527 test "returns map with fake id and published data" do
528 assert %{
529 "context" => "pleroma:fakecontext",
530 "context_id" => -1,
531 "id" => "pleroma:fakeid",
532 "published" => _
533 } = Utils.lazy_put_activity_defaults(%{}, true)
534 end
535
536 test "returns activity data with object" do
537 note_activity = insert(:note_activity)
538 object = Object.normalize(note_activity)
539
540 res =
541 Utils.lazy_put_activity_defaults(%{
542 "context" => object.data["id"],
543 "object" => %{}
544 })
545
546 assert res["context"] == object.data["id"]
547 assert res["context_id"] == object.id
548 assert res["id"]
549 assert res["published"]
550 assert res["object"]["id"]
551 assert res["object"]["published"]
552 assert res["object"]["context"] == object.data["id"]
553 assert res["object"]["context_id"] == object.id
554 end
555 end
556
557 describe "make_flag_data" do
558 test "returns empty map when params is invalid" do
559 assert Utils.make_flag_data(%{}, %{}) == %{}
560 end
561
562 test "returns map with Flag object" do
563 reporter = insert(:user)
564 target_account = insert(:user)
565 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
566 context = Utils.generate_context_id()
567 content = "foobar"
568
569 target_ap_id = target_account.ap_id
570 activity_ap_id = activity.data["id"]
571
572 res =
573 Utils.make_flag_data(
574 %{
575 actor: reporter,
576 context: context,
577 account: target_account,
578 statuses: [%{"id" => activity.data["id"]}],
579 content: content
580 },
581 %{}
582 )
583
584 assert %{
585 "type" => "Flag",
586 "content" => ^content,
587 "context" => ^context,
588 "object" => [^target_ap_id, ^activity_ap_id],
589 "state" => "open"
590 } = res
591 end
592 end
593
594 describe "add_announce_to_object/2" do
595 test "adds actor to announcement" do
596 user = insert(:user)
597 object = insert(:note)
598
599 activity =
600 insert(:note_activity,
601 data: %{
602 "actor" => user.ap_id,
603 "cc" => [Pleroma.Constants.as_public()]
604 }
605 )
606
607 assert {:ok, updated_object} = Utils.add_announce_to_object(activity, object)
608 assert updated_object.data["announcements"] == [user.ap_id]
609 assert updated_object.data["announcement_count"] == 1
610 end
611 end
612
613 describe "remove_announce_from_object/2" do
614 test "removes actor from announcements" do
615 user = insert(:user)
616 user2 = insert(:user)
617
618 object =
619 insert(:note,
620 data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2}
621 )
622
623 activity = insert(:note_activity, data: %{"actor" => user.ap_id})
624
625 assert {:ok, updated_object} = Utils.remove_announce_from_object(activity, object)
626 assert updated_object.data["announcements"] == [user2.ap_id]
627 assert updated_object.data["announcement_count"] == 1
628 end
629 end
630 end