logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma

object_test.exs (13923B)


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