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