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