Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into features/remote...
[akkoma] / test / object_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.ObjectTest do
6 use Pleroma.DataCase
7 use Oban.Testing, repo: Pleroma.Repo
8 import ExUnit.CaptureLog
9 import Pleroma.Factory
10 import Tesla.Mock
11 alias Pleroma.Activity
12 alias Pleroma.Object
13 alias Pleroma.Repo
14 alias Pleroma.Tests.ObanHelpers
15 alias Pleroma.Web.CommonAPI
16
17 setup do
18 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
19 :ok
20 end
21
22 test "returns an object by it's AP id" do
23 object = insert(:note)
24 found_object = Object.get_by_ap_id(object.data["id"])
25
26 assert object == found_object
27 end
28
29 describe "generic changeset" do
30 test "it ensures uniqueness of the id" do
31 object = insert(:note)
32 cs = Object.change(%Object{}, %{data: %{id: object.data["id"]}})
33 assert cs.valid?
34
35 {:error, _result} = Repo.insert(cs)
36 end
37 end
38
39 describe "deletion function" do
40 test "deletes an object" do
41 object = insert(:note)
42 found_object = Object.get_by_ap_id(object.data["id"])
43
44 assert object == found_object
45
46 Object.delete(found_object)
47
48 found_object = Object.get_by_ap_id(object.data["id"])
49
50 refute object == found_object
51
52 assert found_object.data["type"] == "Tombstone"
53 end
54
55 test "ensures cache is cleared for the object" do
56 object = insert(:note)
57 cached_object = Object.get_cached_by_ap_id(object.data["id"])
58
59 assert object == cached_object
60
61 Cachex.put(:web_resp_cache, URI.parse(object.data["id"]).path, "cofe")
62
63 Object.delete(cached_object)
64
65 {:ok, nil} = Cachex.get(:object_cache, "object:#{object.data["id"]}")
66 {:ok, nil} = Cachex.get(:web_resp_cache, URI.parse(object.data["id"]).path)
67
68 cached_object = Object.get_cached_by_ap_id(object.data["id"])
69
70 refute object == cached_object
71
72 assert cached_object.data["type"] == "Tombstone"
73 end
74 end
75
76 describe "delete attachments" do
77 clear_config([Pleroma.Upload])
78
79 test "in subdirectories" do
80 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
81
82 file = %Plug.Upload{
83 content_type: "image/jpg",
84 path: Path.absname("test/fixtures/image.jpg"),
85 filename: "an_image.jpg"
86 }
87
88 user = insert(:user)
89
90 {:ok, %Object{} = attachment} =
91 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
92
93 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
94 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
95
96 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
97
98 path = href |> Path.dirname() |> Path.basename()
99
100 assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
101
102 Object.delete(note)
103
104 ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
105
106 assert Object.get_by_id(attachment.id) == nil
107
108 assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
109 end
110
111 test "with dedupe enabled" do
112 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
113 Pleroma.Config.put([Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Dedupe])
114
115 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
116
117 File.mkdir_p!(uploads_dir)
118
119 file = %Plug.Upload{
120 content_type: "image/jpg",
121 path: Path.absname("test/fixtures/image.jpg"),
122 filename: "an_image.jpg"
123 }
124
125 user = insert(:user)
126
127 {:ok, %Object{} = attachment} =
128 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
129
130 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
131 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
132
133 filename = Path.basename(href)
134
135 assert {:ok, files} = File.ls(uploads_dir)
136 assert filename in files
137
138 Object.delete(note)
139
140 ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
141
142 assert Object.get_by_id(attachment.id) == nil
143 assert {:ok, files} = File.ls(uploads_dir)
144 refute filename in files
145 end
146
147 test "with objects that have legacy data.url attribute" do
148 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
149
150 file = %Plug.Upload{
151 content_type: "image/jpg",
152 path: Path.absname("test/fixtures/image.jpg"),
153 filename: "an_image.jpg"
154 }
155
156 user = insert(:user)
157
158 {:ok, %Object{} = attachment} =
159 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
160
161 {:ok, %Object{}} = Object.create(%{url: "https://google.com", actor: user.ap_id})
162
163 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
164 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
165
166 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
167
168 path = href |> Path.dirname() |> Path.basename()
169
170 assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
171
172 Object.delete(note)
173
174 ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
175
176 assert Object.get_by_id(attachment.id) == nil
177
178 assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
179 end
180
181 test "With custom base_url" do
182 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
183 Pleroma.Config.put([Pleroma.Upload, :base_url], "https://sub.domain.tld/dir/")
184
185 file = %Plug.Upload{
186 content_type: "image/jpg",
187 path: Path.absname("test/fixtures/image.jpg"),
188 filename: "an_image.jpg"
189 }
190
191 user = insert(:user)
192
193 {:ok, %Object{} = attachment} =
194 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
195
196 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
197 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
198
199 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
200
201 path = href |> Path.dirname() |> Path.basename()
202
203 assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
204
205 Object.delete(note)
206
207 ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
208
209 assert Object.get_by_id(attachment.id) == nil
210
211 assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
212 end
213 end
214
215 describe "normalizer" do
216 test "fetches unknown objects by default" do
217 %Object{} =
218 object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367")
219
220 assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
221 end
222
223 test "fetches unknown objects when fetch_remote is explicitly true" do
224 %Object{} =
225 object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367", true)
226
227 assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
228 end
229
230 test "does not fetch unknown objects when fetch_remote is false" do
231 assert is_nil(
232 Object.normalize("http://mastodon.example.org/@admin/99541947525187367", false)
233 )
234 end
235 end
236
237 describe "get_by_id_and_maybe_refetch" do
238 setup do
239 mock(fn
240 %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
241 %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/poll_original.json")}
242
243 env ->
244 apply(HttpRequestMock, :request, [env])
245 end)
246
247 mock_modified = fn resp ->
248 mock(fn
249 %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
250 resp
251
252 env ->
253 apply(HttpRequestMock, :request, [env])
254 end)
255 end
256
257 on_exit(fn -> mock(fn env -> apply(HttpRequestMock, :request, [env]) end) end)
258
259 [mock_modified: mock_modified]
260 end
261
262 test "refetches if the time since the last refetch is greater than the interval", %{
263 mock_modified: mock_modified
264 } do
265 %Object{} =
266 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
267
268 Object.set_cache(object)
269
270 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
271 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
272
273 mock_modified.(%Tesla.Env{
274 status: 200,
275 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
276 })
277
278 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
279 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
280 assert updated_object == object_in_cache
281 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
282 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
283 end
284
285 test "returns the old object if refetch fails", %{mock_modified: mock_modified} do
286 %Object{} =
287 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
288
289 Object.set_cache(object)
290
291 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
292 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
293
294 assert capture_log(fn ->
295 mock_modified.(%Tesla.Env{status: 404, body: ""})
296
297 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
298 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
299 assert updated_object == object_in_cache
300 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
301 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
302 end) =~
303 "[error] Couldn't refresh https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"
304 end
305
306 test "does not refetch if the time since the last refetch is greater than the interval", %{
307 mock_modified: mock_modified
308 } do
309 %Object{} =
310 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
311
312 Object.set_cache(object)
313
314 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
315 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
316
317 mock_modified.(%Tesla.Env{
318 status: 200,
319 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
320 })
321
322 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: 100)
323 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
324 assert updated_object == object_in_cache
325 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
326 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
327 end
328
329 test "preserves internal fields on refetch", %{mock_modified: mock_modified} do
330 %Object{} =
331 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
332
333 Object.set_cache(object)
334
335 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
336 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
337
338 user = insert(:user)
339 activity = Activity.get_create_by_object_ap_id(object.data["id"])
340 {:ok, _activity, object} = CommonAPI.favorite(activity.id, user)
341
342 assert object.data["like_count"] == 1
343
344 mock_modified.(%Tesla.Env{
345 status: 200,
346 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
347 })
348
349 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
350 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
351 assert updated_object == object_in_cache
352 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
353 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
354
355 assert updated_object.data["like_count"] == 1
356 end
357 end
358 end