Merge branch 'develop' into feature/tag_feed
[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 import ExUnit.CaptureLog
8 import Pleroma.Factory
9 import Tesla.Mock
10 alias Pleroma.Activity
11 alias Pleroma.Object
12 alias Pleroma.Repo
13 alias Pleroma.Web.CommonAPI
14
15 setup do
16 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
17 :ok
18 end
19
20 test "returns an object by it's AP id" do
21 object = insert(:note)
22 found_object = Object.get_by_ap_id(object.data["id"])
23
24 assert object == found_object
25 end
26
27 describe "generic changeset" do
28 test "it ensures uniqueness of the id" do
29 object = insert(:note)
30 cs = Object.change(%Object{}, %{data: %{id: object.data["id"]}})
31 assert cs.valid?
32
33 {:error, _result} = Repo.insert(cs)
34 end
35 end
36
37 describe "deletion function" do
38 test "deletes an object" do
39 object = insert(:note)
40 found_object = Object.get_by_ap_id(object.data["id"])
41
42 assert object == found_object
43
44 Object.delete(found_object)
45
46 found_object = Object.get_by_ap_id(object.data["id"])
47
48 refute object == found_object
49
50 assert found_object.data["type"] == "Tombstone"
51 end
52
53 test "ensures cache is cleared for the object" do
54 object = insert(:note)
55 cached_object = Object.get_cached_by_ap_id(object.data["id"])
56
57 assert object == cached_object
58
59 Cachex.put(:web_resp_cache, URI.parse(object.data["id"]).path, "cofe")
60
61 Object.delete(cached_object)
62
63 {:ok, nil} = Cachex.get(:object_cache, "object:#{object.data["id"]}")
64 {:ok, nil} = Cachex.get(:web_resp_cache, URI.parse(object.data["id"]).path)
65
66 cached_object = Object.get_cached_by_ap_id(object.data["id"])
67
68 refute object == cached_object
69
70 assert cached_object.data["type"] == "Tombstone"
71 end
72 end
73
74 describe "delete attachments" do
75 clear_config([Pleroma.Upload])
76
77 test "in subdirectories" do
78 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
79
80 file = %Plug.Upload{
81 content_type: "image/jpg",
82 path: Path.absname("test/fixtures/image.jpg"),
83 filename: "an_image.jpg"
84 }
85
86 user = insert(:user)
87
88 {:ok, %Object{} = attachment} =
89 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
90
91 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
92 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
93
94 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
95
96 path = href |> Path.dirname() |> Path.basename()
97
98 assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}")
99
100 Object.delete(note)
101
102 assert Object.get_by_id(attachment.id) == nil
103
104 assert {:ok, []} == File.ls("#{uploads_dir}/#{path}")
105 end
106
107 test "with dedupe enabled" do
108 Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
109 Pleroma.Config.put([Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Dedupe])
110
111 uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
112
113 File.mkdir_p!(uploads_dir)
114
115 file = %Plug.Upload{
116 content_type: "image/jpg",
117 path: Path.absname("test/fixtures/image.jpg"),
118 filename: "an_image.jpg"
119 }
120
121 user = insert(:user)
122
123 {:ok, %Object{} = attachment} =
124 Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
125
126 %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} =
127 note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
128
129 filename = Path.basename(href)
130
131 assert {:ok, files} = File.ls(uploads_dir)
132 assert filename in files
133
134 Object.delete(note)
135
136 assert Object.get_by_id(attachment.id) == nil
137 assert {:ok, files} = File.ls(uploads_dir)
138 refute filename in files
139 end
140 end
141
142 describe "normalizer" do
143 test "fetches unknown objects by default" do
144 %Object{} =
145 object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367")
146
147 assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
148 end
149
150 test "fetches unknown objects when fetch_remote is explicitly true" do
151 %Object{} =
152 object = Object.normalize("http://mastodon.example.org/@admin/99541947525187367", true)
153
154 assert object.data["url"] == "http://mastodon.example.org/@admin/99541947525187367"
155 end
156
157 test "does not fetch unknown objects when fetch_remote is false" do
158 assert is_nil(
159 Object.normalize("http://mastodon.example.org/@admin/99541947525187367", false)
160 )
161 end
162 end
163
164 describe "get_by_id_and_maybe_refetch" do
165 setup do
166 mock(fn
167 %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
168 %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/poll_original.json")}
169
170 env ->
171 apply(HttpRequestMock, :request, [env])
172 end)
173
174 mock_modified = fn resp ->
175 mock(fn
176 %{method: :get, url: "https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"} ->
177 resp
178
179 env ->
180 apply(HttpRequestMock, :request, [env])
181 end)
182 end
183
184 on_exit(fn -> mock(fn env -> apply(HttpRequestMock, :request, [env]) end) end)
185
186 [mock_modified: mock_modified]
187 end
188
189 test "refetches if the time since the last refetch is greater than the interval", %{
190 mock_modified: mock_modified
191 } do
192 %Object{} =
193 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
194
195 Object.set_cache(object)
196
197 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
198 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
199
200 mock_modified.(%Tesla.Env{
201 status: 200,
202 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
203 })
204
205 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
206 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
207 assert updated_object == object_in_cache
208 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
209 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
210 end
211
212 test "returns the old object if refetch fails", %{mock_modified: mock_modified} do
213 %Object{} =
214 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
215
216 Object.set_cache(object)
217
218 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
219 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
220
221 assert capture_log(fn ->
222 mock_modified.(%Tesla.Env{status: 404, body: ""})
223
224 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
225 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
226 assert updated_object == object_in_cache
227 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
228 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
229 end) =~
230 "[error] Couldn't refresh https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d"
231 end
232
233 test "does not refetch if the time since the last refetch is greater than the interval", %{
234 mock_modified: mock_modified
235 } do
236 %Object{} =
237 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
238
239 Object.set_cache(object)
240
241 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
242 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
243
244 mock_modified.(%Tesla.Env{
245 status: 200,
246 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
247 })
248
249 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: 100)
250 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
251 assert updated_object == object_in_cache
252 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 4
253 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 0
254 end
255
256 test "preserves internal fields on refetch", %{mock_modified: mock_modified} do
257 %Object{} =
258 object = Object.normalize("https://patch.cx/objects/9a172665-2bc5-452d-8428-2361d4c33b1d")
259
260 Object.set_cache(object)
261
262 assert Enum.at(object.data["oneOf"], 0)["replies"]["totalItems"] == 4
263 assert Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 0
264
265 user = insert(:user)
266 activity = Activity.get_create_by_object_ap_id(object.data["id"])
267 {:ok, _activity, object} = CommonAPI.favorite(activity.id, user)
268
269 assert object.data["like_count"] == 1
270
271 mock_modified.(%Tesla.Env{
272 status: 200,
273 body: File.read!("test/fixtures/tesla_mock/poll_modified.json")
274 })
275
276 updated_object = Object.get_by_id_and_maybe_refetch(object.id, interval: -1)
277 object_in_cache = Object.get_cached_by_ap_id(object.data["id"])
278 assert updated_object == object_in_cache
279 assert Enum.at(updated_object.data["oneOf"], 0)["replies"]["totalItems"] == 8
280 assert Enum.at(updated_object.data["oneOf"], 1)["replies"]["totalItems"] == 3
281
282 assert updated_object.data["like_count"] == 1
283 end
284 end
285 end