da26b13f7040b8f880e1296231970e4cc4986512
[akkoma] / test / web / activity_pub / publisher_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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 clear_config_all([:instance, :federating]) do
27 Pleroma.Config.put([:instance, :federating], true)
28 end
29
30 describe "gather_webfinger_links/1" do
31 test "it returns links" do
32 user = insert(:user)
33
34 expected_links = [
35 %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"},
36 %{
37 "href" => user.ap_id,
38 "rel" => "self",
39 "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
40 },
41 %{
42 "rel" => "http://ostatus.org/schema/1.0/subscribe",
43 "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
44 }
45 ]
46
47 assert expected_links == Publisher.gather_webfinger_links(user)
48 end
49 end
50
51 describe "determine_inbox/2" do
52 test "it returns sharedInbox for messages involving as:Public in to" do
53 user =
54 insert(:user, %{
55 source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
56 })
57
58 activity = %Activity{
59 data: %{"to" => [@as_public], "cc" => [user.follower_address]}
60 }
61
62 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
63 end
64
65 test "it returns sharedInbox for messages involving as:Public in cc" do
66 user =
67 insert(:user, %{
68 source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
69 })
70
71 activity = %Activity{
72 data: %{"cc" => [@as_public], "to" => [user.follower_address]}
73 }
74
75 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
76 end
77
78 test "it returns sharedInbox for messages involving multiple recipients in to" do
79 user =
80 insert(:user, %{
81 source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
82 })
83
84 user_two = insert(:user)
85 user_three = insert(:user)
86
87 activity = %Activity{
88 data: %{"cc" => [], "to" => [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 cc" do
95 user =
96 insert(:user, %{
97 source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
98 })
99
100 user_two = insert(:user)
101 user_three = insert(:user)
102
103 activity = %Activity{
104 data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
105 }
106
107 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
108 end
109
110 test "it returns sharedInbox for messages involving multiple recipients in total" do
111 user =
112 insert(:user,
113 source_data: %{
114 "inbox" => "http://example.com/personal-inbox",
115 "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
116 }
117 )
118
119 user_two = insert(:user)
120
121 activity = %Activity{
122 data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
123 }
124
125 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
126 end
127
128 test "it returns inbox for messages involving single recipients in total" do
129 user =
130 insert(:user,
131 source_data: %{
132 "inbox" => "http://example.com/personal-inbox",
133 "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
134 }
135 )
136
137 activity = %Activity{
138 data: %{"to" => [user.ap_id], "cc" => []}
139 }
140
141 assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
142 end
143 end
144
145 describe "publish_one/1" do
146 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
147 Instances,
148 [:passthrough],
149 [] do
150 actor = insert(:user)
151 inbox = "http://200.site/users/nick1/inbox"
152
153 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
154
155 assert called(Instances.set_reachable(inbox))
156 end
157
158 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
159 Instances,
160 [:passthrough],
161 [] do
162 actor = insert(:user)
163 inbox = "http://200.site/users/nick1/inbox"
164
165 assert {:ok, _} =
166 Publisher.publish_one(%{
167 inbox: inbox,
168 json: "{}",
169 actor: actor,
170 id: 1,
171 unreachable_since: NaiveDateTime.utc_now()
172 })
173
174 assert called(Instances.set_reachable(inbox))
175 end
176
177 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
178 Instances,
179 [:passthrough],
180 [] do
181 actor = insert(:user)
182 inbox = "http://200.site/users/nick1/inbox"
183
184 assert {:ok, _} =
185 Publisher.publish_one(%{
186 inbox: inbox,
187 json: "{}",
188 actor: actor,
189 id: 1,
190 unreachable_since: nil
191 })
192
193 refute called(Instances.set_reachable(inbox))
194 end
195
196 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
197 Instances,
198 [:passthrough],
199 [] do
200 actor = insert(:user)
201 inbox = "http://404.site/users/nick1/inbox"
202
203 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
204
205 assert called(Instances.set_unreachable(inbox))
206 end
207
208 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
209 Instances,
210 [:passthrough],
211 [] do
212 actor = insert(:user)
213 inbox = "http://connrefused.site/users/nick1/inbox"
214
215 assert capture_log(fn ->
216 assert {:error, _} =
217 Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
218 end) =~ "connrefused"
219
220 assert called(Instances.set_unreachable(inbox))
221 end
222
223 test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
224 Instances,
225 [:passthrough],
226 [] do
227 actor = insert(:user)
228 inbox = "http://200.site/users/nick1/inbox"
229
230 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
231
232 refute called(Instances.set_unreachable(inbox))
233 end
234
235 test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
236 Instances,
237 [:passthrough],
238 [] do
239 actor = insert(:user)
240 inbox = "http://connrefused.site/users/nick1/inbox"
241
242 assert capture_log(fn ->
243 assert {:error, _} =
244 Publisher.publish_one(%{
245 inbox: inbox,
246 json: "{}",
247 actor: actor,
248 id: 1,
249 unreachable_since: NaiveDateTime.utc_now()
250 })
251 end) =~ "connrefused"
252
253 refute called(Instances.set_unreachable(inbox))
254 end
255 end
256
257 describe "publish/2" do
258 test_with_mock "publishes an activity with BCC to all relevant peers.",
259 Pleroma.Web.Federator.Publisher,
260 [:passthrough],
261 [] do
262 follower =
263 insert(:user,
264 local: false,
265 source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
266 ap_enabled: true
267 )
268
269 actor = insert(:user, follower_address: follower.ap_id)
270 user = insert(:user)
271
272 {:ok, _follower_one} = Pleroma.User.follow(follower, actor)
273 actor = refresh_record(actor)
274
275 note_activity =
276 insert(:note_activity,
277 recipients: [follower.ap_id],
278 data_attrs: %{"bcc" => [user.ap_id]}
279 )
280
281 res = Publisher.publish(actor, note_activity)
282 assert res == :ok
283
284 assert called(
285 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
286 inbox: "https://domain.com/users/nick1/inbox",
287 actor_id: actor.id,
288 id: note_activity.data["id"]
289 })
290 )
291 end
292
293 test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
294 Pleroma.Web.Federator.Publisher,
295 [:passthrough],
296 [] do
297 fetcher =
298 insert(:user,
299 local: false,
300 source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
301 ap_enabled: true
302 )
303
304 another_fetcher =
305 insert(:user,
306 local: false,
307 source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"},
308 ap_enabled: true
309 )
310
311 actor = insert(:user)
312
313 note_activity = insert(:note_activity, user: actor)
314 object = Object.normalize(note_activity)
315
316 activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
317 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
318
319 build_conn()
320 |> put_req_header("accept", "application/activity+json")
321 |> assign(:user, fetcher)
322 |> get(object_path)
323 |> json_response(200)
324
325 build_conn()
326 |> put_req_header("accept", "application/activity+json")
327 |> assign(:user, another_fetcher)
328 |> get(activity_path)
329 |> json_response(200)
330
331 {:ok, delete} = CommonAPI.delete(note_activity.id, actor)
332
333 res = Publisher.publish(actor, delete)
334 assert res == :ok
335
336 assert called(
337 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
338 inbox: "https://domain.com/users/nick1/inbox",
339 actor_id: actor.id,
340 id: delete.data["id"]
341 })
342 )
343
344 assert called(
345 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
346 inbox: "https://domain2.com/users/nick1/inbox",
347 actor_id: actor.id,
348 id: delete.data["id"]
349 })
350 )
351 end
352 end
353 end