Merge remote-tracking branch 'origin/develop' into manifest
[akkoma] / test / pleroma / web / activity_pub / publisher_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.PublisherTest do
6 use Pleroma.Web.ConnCase
7
8 import ExUnit.CaptureLog
9 import Pleroma.Factory
10 import Tesla.Mock
11 import Mock
12
13 alias Pleroma.Activity
14 alias Pleroma.Instances
15 alias Pleroma.Object
16 alias Pleroma.Web.ActivityPub.Publisher
17 alias Pleroma.Web.CommonAPI
18
19 @as_public "https://www.w3.org/ns/activitystreams#Public"
20
21 setup do
22 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
23 :ok
24 end
25
26 setup_all do: clear_config([:instance, :federating], true)
27
28 describe "gather_webfinger_links/1" do
29 test "it returns links" do
30 user = insert(:user)
31
32 expected_links = [
33 %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"},
34 %{
35 "href" => user.ap_id,
36 "rel" => "self",
37 "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
38 },
39 %{
40 "rel" => "http://ostatus.org/schema/1.0/subscribe",
41 "template" => "#{Pleroma.Web.Endpoint.url()}/ostatus_subscribe?acct={uri}"
42 }
43 ]
44
45 assert expected_links == Publisher.gather_webfinger_links(user)
46 end
47 end
48
49 describe "determine_inbox/2" do
50 test "it returns sharedInbox for messages involving as:Public in to" do
51 user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
52
53 activity = %Activity{
54 data: %{"to" => [@as_public], "cc" => [user.follower_address]}
55 }
56
57 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
58 end
59
60 test "it returns sharedInbox for messages involving as:Public in cc" do
61 user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
62
63 activity = %Activity{
64 data: %{"cc" => [@as_public], "to" => [user.follower_address]}
65 }
66
67 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
68 end
69
70 test "it returns sharedInbox for messages involving multiple recipients in to" do
71 user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
72 user_two = insert(:user)
73 user_three = insert(:user)
74
75 activity = %Activity{
76 data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
77 }
78
79 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
80 end
81
82 test "it returns sharedInbox for messages involving multiple recipients in cc" do
83 user = insert(:user, %{shared_inbox: "http://example.com/inbox"})
84 user_two = insert(:user)
85 user_three = insert(:user)
86
87 activity = %Activity{
88 data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
89 }
90
91 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
92 end
93
94 test "it returns sharedInbox for messages involving multiple recipients in total" do
95 user =
96 insert(:user, %{
97 shared_inbox: "http://example.com/inbox",
98 inbox: "http://example.com/personal-inbox"
99 })
100
101 user_two = insert(:user)
102
103 activity = %Activity{
104 data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
105 }
106
107 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
108 end
109
110 test "it returns inbox for messages involving single recipients in total" do
111 user =
112 insert(:user, %{
113 shared_inbox: "http://example.com/inbox",
114 inbox: "http://example.com/personal-inbox"
115 })
116
117 activity = %Activity{
118 data: %{"to" => [user.ap_id], "cc" => []}
119 }
120
121 assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
122 end
123 end
124
125 describe "publish_one/1" do
126 test "publish to url with with different ports" do
127 inbox80 = "http://42.site/users/nick1/inbox"
128 inbox42 = "http://42.site:42/users/nick1/inbox"
129
130 mock(fn
131 %{method: :post, url: "http://42.site:42/users/nick1/inbox"} ->
132 {:ok, %Tesla.Env{status: 200, body: "port 42"}}
133
134 %{method: :post, url: "http://42.site/users/nick1/inbox"} ->
135 {:ok, %Tesla.Env{status: 200, body: "port 80"}}
136 end)
137
138 actor = insert(:user)
139
140 assert {:ok, %{body: "port 42"}} =
141 Publisher.publish_one(%{
142 inbox: inbox42,
143 json: "{}",
144 actor: actor,
145 id: 1,
146 unreachable_since: true
147 })
148
149 assert {:ok, %{body: "port 80"}} =
150 Publisher.publish_one(%{
151 inbox: inbox80,
152 json: "{}",
153 actor: actor,
154 id: 1,
155 unreachable_since: true
156 })
157 end
158
159 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
160 Instances,
161 [:passthrough],
162 [] do
163 actor = insert(:user)
164 inbox = "http://200.site/users/nick1/inbox"
165
166 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
167 assert called(Instances.set_reachable(inbox))
168 end
169
170 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
171 Instances,
172 [:passthrough],
173 [] do
174 actor = insert(:user)
175 inbox = "http://200.site/users/nick1/inbox"
176
177 assert {:ok, _} =
178 Publisher.publish_one(%{
179 inbox: inbox,
180 json: "{}",
181 actor: actor,
182 id: 1,
183 unreachable_since: NaiveDateTime.utc_now()
184 })
185
186 assert called(Instances.set_reachable(inbox))
187 end
188
189 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
190 Instances,
191 [:passthrough],
192 [] do
193 actor = insert(:user)
194 inbox = "http://200.site/users/nick1/inbox"
195
196 assert {:ok, _} =
197 Publisher.publish_one(%{
198 inbox: inbox,
199 json: "{}",
200 actor: actor,
201 id: 1,
202 unreachable_since: nil
203 })
204
205 refute called(Instances.set_reachable(inbox))
206 end
207
208 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
209 Instances,
210 [:passthrough],
211 [] do
212 actor = insert(:user)
213 inbox = "http://404.site/users/nick1/inbox"
214
215 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
216
217 assert called(Instances.set_unreachable(inbox))
218 end
219
220 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
221 Instances,
222 [:passthrough],
223 [] do
224 actor = insert(:user)
225 inbox = "http://connrefused.site/users/nick1/inbox"
226
227 assert capture_log(fn ->
228 assert {:error, _} =
229 Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
230 end) =~ "connrefused"
231
232 assert called(Instances.set_unreachable(inbox))
233 end
234
235 test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
236 Instances,
237 [:passthrough],
238 [] do
239 actor = insert(:user)
240 inbox = "http://200.site/users/nick1/inbox"
241
242 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
243
244 refute called(Instances.set_unreachable(inbox))
245 end
246
247 test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
248 Instances,
249 [:passthrough],
250 [] do
251 actor = insert(:user)
252 inbox = "http://connrefused.site/users/nick1/inbox"
253
254 assert capture_log(fn ->
255 assert {:error, _} =
256 Publisher.publish_one(%{
257 inbox: inbox,
258 json: "{}",
259 actor: actor,
260 id: 1,
261 unreachable_since: NaiveDateTime.utc_now()
262 })
263 end) =~ "connrefused"
264
265 refute called(Instances.set_unreachable(inbox))
266 end
267 end
268
269 describe "publish/2" do
270 test_with_mock "doesn't publish a non-public activity to quarantined instances.",
271 Pleroma.Web.Federator.Publisher,
272 [:passthrough],
273 [] do
274 Config.put([:instance, :quarantined_instances], [{"domain.com", "some reason"}])
275
276 follower =
277 insert(:user, %{
278 local: false,
279 inbox: "https://domain.com/users/nick1/inbox",
280 ap_enabled: true
281 })
282
283 actor = insert(:user, follower_address: follower.ap_id)
284
285 {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
286 actor = refresh_record(actor)
287
288 note_activity =
289 insert(:followers_only_note_activity,
290 user: actor,
291 recipients: [follower.ap_id]
292 )
293
294 res = Publisher.publish(actor, note_activity)
295
296 assert res == :ok
297
298 assert not called(
299 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
300 inbox: "https://domain.com/users/nick1/inbox",
301 actor_id: actor.id,
302 id: note_activity.data["id"]
303 })
304 )
305 end
306
307 test_with_mock "Publishes a non-public activity to non-quarantined instances.",
308 Pleroma.Web.Federator.Publisher,
309 [:passthrough],
310 [] do
311 Config.put([:instance, :quarantined_instances], [{"somedomain.com", "some reason"}])
312
313 follower =
314 insert(:user, %{
315 local: false,
316 inbox: "https://domain.com/users/nick1/inbox",
317 ap_enabled: true
318 })
319
320 actor = insert(:user, follower_address: follower.ap_id)
321
322 {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
323 actor = refresh_record(actor)
324
325 note_activity =
326 insert(:followers_only_note_activity,
327 user: actor,
328 recipients: [follower.ap_id]
329 )
330
331 res = Publisher.publish(actor, note_activity)
332
333 assert res == :ok
334
335 assert called(
336 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
337 inbox: "https://domain.com/users/nick1/inbox",
338 actor_id: actor.id,
339 id: note_activity.data["id"]
340 })
341 )
342 end
343
344 test_with_mock "publishes an activity with BCC to all relevant peers.",
345 Pleroma.Web.Federator.Publisher,
346 [:passthrough],
347 [] do
348 follower =
349 insert(:user, %{
350 local: false,
351 inbox: "https://domain.com/users/nick1/inbox",
352 ap_enabled: true
353 })
354
355 actor = insert(:user, follower_address: follower.ap_id)
356 user = insert(:user)
357
358 {:ok, follower, actor} = Pleroma.User.follow(follower, actor)
359
360 note_activity =
361 insert(:note_activity,
362 recipients: [follower.ap_id],
363 data_attrs: %{"bcc" => [user.ap_id]}
364 )
365
366 res = Publisher.publish(actor, note_activity)
367 assert res == :ok
368
369 assert called(
370 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
371 inbox: "https://domain.com/users/nick1/inbox",
372 actor_id: actor.id,
373 id: note_activity.data["id"]
374 })
375 )
376 end
377
378 test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
379 Pleroma.Web.Federator.Publisher,
380 [:passthrough],
381 [] do
382 fetcher =
383 insert(:user,
384 local: false,
385 inbox: "https://domain.com/users/nick1/inbox",
386 ap_enabled: true
387 )
388
389 another_fetcher =
390 insert(:user,
391 local: false,
392 inbox: "https://domain2.com/users/nick1/inbox",
393 ap_enabled: true
394 )
395
396 actor = insert(:user)
397
398 note_activity = insert(:note_activity, user: actor)
399 object = Object.normalize(note_activity, fetch: false)
400
401 activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
402 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
403
404 build_conn()
405 |> put_req_header("accept", "application/activity+json")
406 |> assign(:user, fetcher)
407 |> get(object_path)
408 |> json_response(200)
409
410 build_conn()
411 |> put_req_header("accept", "application/activity+json")
412 |> assign(:user, another_fetcher)
413 |> get(activity_path)
414 |> json_response(200)
415
416 {:ok, delete} = CommonAPI.delete(note_activity.id, actor)
417
418 res = Publisher.publish(actor, delete)
419 assert res == :ok
420
421 assert called(
422 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
423 inbox: "https://domain.com/users/nick1/inbox",
424 actor_id: actor.id,
425 id: delete.data["id"]
426 })
427 )
428
429 assert called(
430 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
431 inbox: "https://domain2.com/users/nick1/inbox",
432 actor_id: actor.id,
433 id: delete.data["id"]
434 })
435 )
436 end
437 end
438 end