Merge branch 'develop' into 'remove-twitter-api'
[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 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.base_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_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
127 Instances,
128 [:passthrough],
129 [] do
130 actor = insert(:user)
131 inbox = "http://200.site/users/nick1/inbox"
132
133 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
134
135 assert called(Instances.set_reachable(inbox))
136 end
137
138 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
139 Instances,
140 [:passthrough],
141 [] do
142 actor = insert(:user)
143 inbox = "http://200.site/users/nick1/inbox"
144
145 assert {:ok, _} =
146 Publisher.publish_one(%{
147 inbox: inbox,
148 json: "{}",
149 actor: actor,
150 id: 1,
151 unreachable_since: NaiveDateTime.utc_now()
152 })
153
154 assert called(Instances.set_reachable(inbox))
155 end
156
157 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
158 Instances,
159 [:passthrough],
160 [] do
161 actor = insert(:user)
162 inbox = "http://200.site/users/nick1/inbox"
163
164 assert {:ok, _} =
165 Publisher.publish_one(%{
166 inbox: inbox,
167 json: "{}",
168 actor: actor,
169 id: 1,
170 unreachable_since: nil
171 })
172
173 refute called(Instances.set_reachable(inbox))
174 end
175
176 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
177 Instances,
178 [:passthrough],
179 [] do
180 actor = insert(:user)
181 inbox = "http://404.site/users/nick1/inbox"
182
183 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
184
185 assert called(Instances.set_unreachable(inbox))
186 end
187
188 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
189 Instances,
190 [:passthrough],
191 [] do
192 actor = insert(:user)
193 inbox = "http://connrefused.site/users/nick1/inbox"
194
195 assert capture_log(fn ->
196 assert {:error, _} =
197 Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
198 end) =~ "connrefused"
199
200 assert called(Instances.set_unreachable(inbox))
201 end
202
203 test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
204 Instances,
205 [:passthrough],
206 [] do
207 actor = insert(:user)
208 inbox = "http://200.site/users/nick1/inbox"
209
210 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
211
212 refute called(Instances.set_unreachable(inbox))
213 end
214
215 test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
216 Instances,
217 [:passthrough],
218 [] do
219 actor = insert(:user)
220 inbox = "http://connrefused.site/users/nick1/inbox"
221
222 assert capture_log(fn ->
223 assert {:error, _} =
224 Publisher.publish_one(%{
225 inbox: inbox,
226 json: "{}",
227 actor: actor,
228 id: 1,
229 unreachable_since: NaiveDateTime.utc_now()
230 })
231 end) =~ "connrefused"
232
233 refute called(Instances.set_unreachable(inbox))
234 end
235 end
236
237 describe "publish/2" do
238 test_with_mock "publishes an activity with BCC to all relevant peers.",
239 Pleroma.Web.Federator.Publisher,
240 [:passthrough],
241 [] do
242 follower =
243 insert(:user, %{
244 local: false,
245 inbox: "https://domain.com/users/nick1/inbox",
246 ap_enabled: true
247 })
248
249 actor = insert(:user, follower_address: follower.ap_id)
250 user = insert(:user)
251
252 {:ok, _follower_one} = Pleroma.User.follow(follower, actor)
253 actor = refresh_record(actor)
254
255 note_activity =
256 insert(:note_activity,
257 recipients: [follower.ap_id],
258 data_attrs: %{"bcc" => [user.ap_id]}
259 )
260
261 res = Publisher.publish(actor, note_activity)
262 assert res == :ok
263
264 assert called(
265 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
266 inbox: "https://domain.com/users/nick1/inbox",
267 actor_id: actor.id,
268 id: note_activity.data["id"]
269 })
270 )
271 end
272
273 test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.",
274 Pleroma.Web.Federator.Publisher,
275 [:passthrough],
276 [] do
277 fetcher =
278 insert(:user,
279 local: false,
280 inbox: "https://domain.com/users/nick1/inbox",
281 ap_enabled: true
282 )
283
284 another_fetcher =
285 insert(:user,
286 local: false,
287 inbox: "https://domain2.com/users/nick1/inbox",
288 ap_enabled: true
289 )
290
291 actor = insert(:user)
292
293 note_activity = insert(:note_activity, user: actor)
294 object = Object.normalize(note_activity)
295
296 activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
297 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
298
299 build_conn()
300 |> put_req_header("accept", "application/activity+json")
301 |> assign(:user, fetcher)
302 |> get(object_path)
303 |> json_response(200)
304
305 build_conn()
306 |> put_req_header("accept", "application/activity+json")
307 |> assign(:user, another_fetcher)
308 |> get(activity_path)
309 |> json_response(200)
310
311 {:ok, delete} = CommonAPI.delete(note_activity.id, actor)
312
313 res = Publisher.publish(actor, delete)
314 assert res == :ok
315
316 assert called(
317 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
318 inbox: "https://domain.com/users/nick1/inbox",
319 actor_id: actor.id,
320 id: delete.data["id"]
321 })
322 )
323
324 assert called(
325 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
326 inbox: "https://domain2.com/users/nick1/inbox",
327 actor_id: actor.id,
328 id: delete.data["id"]
329 })
330 )
331 end
332 end
333 end