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