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