branch
[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 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 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 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 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 source_data: %{
89 "inbox" => "http://example.com/personal-inbox",
90 "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
91 }
92 )
93
94 user_two = insert(:user)
95
96 activity = %Activity{
97 data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
98 }
99
100 assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
101 end
102
103 test "it returns inbox for messages involving single recipients in total" do
104 user =
105 insert(:user,
106 source_data: %{
107 "inbox" => "http://example.com/personal-inbox",
108 "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
109 }
110 )
111
112 activity = %Activity{
113 data: %{"to" => [user.ap_id], "cc" => []}
114 }
115
116 assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
117 end
118 end
119
120 describe "publish_one/1" do
121 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
122 Instances,
123 [:passthrough],
124 [] do
125 actor = insert(:user)
126 inbox = "http://200.site/users/nick1/inbox"
127
128 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
129
130 assert called(Instances.set_reachable(inbox))
131 end
132
133 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
134 Instances,
135 [:passthrough],
136 [] do
137 actor = insert(:user)
138 inbox = "http://200.site/users/nick1/inbox"
139
140 assert {:ok, _} =
141 Publisher.publish_one(%{
142 inbox: inbox,
143 json: "{}",
144 actor: actor,
145 id: 1,
146 unreachable_since: NaiveDateTime.utc_now()
147 })
148
149 assert called(Instances.set_reachable(inbox))
150 end
151
152 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
153 Instances,
154 [:passthrough],
155 [] do
156 actor = insert(:user)
157 inbox = "http://200.site/users/nick1/inbox"
158
159 assert {:ok, _} =
160 Publisher.publish_one(%{
161 inbox: inbox,
162 json: "{}",
163 actor: actor,
164 id: 1,
165 unreachable_since: nil
166 })
167
168 refute called(Instances.set_reachable(inbox))
169 end
170
171 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
172 Instances,
173 [:passthrough],
174 [] do
175 actor = insert(:user)
176 inbox = "http://404.site/users/nick1/inbox"
177
178 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
179
180 assert called(Instances.set_unreachable(inbox))
181 end
182
183 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
184 Instances,
185 [:passthrough],
186 [] do
187 actor = insert(:user)
188 inbox = "http://connrefused.site/users/nick1/inbox"
189
190 assert capture_log(fn ->
191 assert {:error, _} =
192 Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
193 end) =~ "connrefused"
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 capture_log(fn ->
218 assert {:error, _} =
219 Publisher.publish_one(%{
220 inbox: inbox,
221 json: "{}",
222 actor: actor,
223 id: 1,
224 unreachable_since: NaiveDateTime.utc_now()
225 })
226 end) =~ "connrefused"
227
228 refute called(Instances.set_unreachable(inbox))
229 end
230 end
231
232 describe "publish/2" do
233 test_with_mock "publishes an activity with BCC to all relevant peers.",
234 Pleroma.Web.Federator.Publisher,
235 [:passthrough],
236 [] do
237 follower =
238 insert(:user,
239 local: false,
240 source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
241 ap_enabled: true
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_id: actor.id,
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 source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
276 ap_enabled: true
277 )
278
279 another_fetcher =
280 insert(:user,
281 local: false,
282 source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"},
283 ap_enabled: true
284 )
285
286 actor = insert(:user)
287
288 note_activity = insert(:note_activity, user: actor)
289 object = Object.normalize(note_activity)
290
291 activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url())
292 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
293
294 build_conn()
295 |> put_req_header("accept", "application/activity+json")
296 |> assign(:user, fetcher)
297 |> get(object_path)
298 |> json_response(200)
299
300 build_conn()
301 |> put_req_header("accept", "application/activity+json")
302 |> assign(:user, another_fetcher)
303 |> get(activity_path)
304 |> json_response(200)
305
306 {:ok, delete} = CommonAPI.delete(note_activity.id, actor)
307
308 res = Publisher.publish(actor, delete)
309 assert res == :ok
310
311 assert called(
312 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
313 inbox: "https://domain.com/users/nick1/inbox",
314 actor_id: actor.id,
315 id: delete.data["id"]
316 })
317 )
318
319 assert called(
320 Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
321 inbox: "https://domain2.com/users/nick1/inbox",
322 actor_id: actor.id,
323 id: delete.data["id"]
324 })
325 )
326 end
327 end
328 end