logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

common_api_test.exs (68712B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.CommonAPITest do
  5. use Oban.Testing, repo: Pleroma.Repo
  6. use Pleroma.DataCase, async: false
  7. alias Pleroma.Activity
  8. alias Pleroma.Chat
  9. alias Pleroma.Conversation.Participation
  10. alias Pleroma.Notification
  11. alias Pleroma.Object
  12. alias Pleroma.Repo
  13. alias Pleroma.Rule
  14. alias Pleroma.Tests.ObanHelpers
  15. alias Pleroma.UnstubbedConfigMock, as: ConfigMock
  16. alias Pleroma.User
  17. alias Pleroma.Web.ActivityPub.ActivityPub
  18. alias Pleroma.Web.ActivityPub.Transmogrifier
  19. alias Pleroma.Web.ActivityPub.Visibility
  20. alias Pleroma.Web.AdminAPI.AccountView
  21. alias Pleroma.Web.CommonAPI
  22. alias Pleroma.Workers.PollWorker
  23. import Ecto.Query, only: [from: 2, where: 3]
  24. import Mock
  25. import Mox
  26. import Pleroma.Factory
  27. require Pleroma.Activity.Queries
  28. require Pleroma.Constants
  29. defp get_announces_of_object(%{data: %{"id" => id}} = _object) do
  30. Pleroma.Activity.Queries.by_type("Announce")
  31. |> Pleroma.Activity.Queries.by_object_id(id)
  32. |> Pleroma.Repo.all()
  33. end
  34. setup_all do
  35. Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
  36. :ok
  37. end
  38. setup do
  39. ConfigMock
  40. |> stub_with(Pleroma.Test.StaticConfig)
  41. :ok
  42. end
  43. setup do: clear_config([:instance, :safe_dm_mentions])
  44. setup do: clear_config([:instance, :limit])
  45. setup do: clear_config([:instance, :max_pinned_statuses])
  46. describe "posting polls" do
  47. test "it posts a poll" do
  48. user = insert(:user)
  49. {:ok, activity} =
  50. CommonAPI.post(user, %{
  51. status: "who is the best",
  52. poll: %{expires_in: 600, options: ["reimu", "marisa"]}
  53. })
  54. object = Object.normalize(activity, fetch: false)
  55. assert object.data["type"] == "Question"
  56. assert object.data["oneOf"] |> length() == 2
  57. assert_enqueued(
  58. worker: PollWorker,
  59. args: %{op: "poll_end", activity_id: activity.id},
  60. scheduled_at: NaiveDateTime.from_iso8601!(object.data["closed"])
  61. )
  62. end
  63. end
  64. describe "blocking" do
  65. setup do
  66. blocker = insert(:user)
  67. blocked = insert(:user, local: false)
  68. CommonAPI.follow(blocked, blocker)
  69. CommonAPI.follow(blocker, blocked)
  70. CommonAPI.accept_follow_request(blocker, blocked)
  71. CommonAPI.accept_follow_request(blocked, blocked)
  72. %{blocker: blocker, blocked: blocked}
  73. end
  74. test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
  75. clear_config([:instance, :federating], true)
  76. with_mock Pleroma.Web.Federator,
  77. publish: fn _ -> nil end do
  78. assert User.get_follow_state(blocker, blocked) == :follow_accept
  79. refute is_nil(Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked))
  80. assert {:ok, block} = CommonAPI.block(blocked, blocker)
  81. assert block.local
  82. assert User.blocks?(blocker, blocked)
  83. refute User.following?(blocker, blocked)
  84. refute User.following?(blocked, blocker)
  85. refute User.get_follow_state(blocker, blocked)
  86. assert %{data: %{"state" => "reject"}} =
  87. Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(blocker, blocked)
  88. assert called(Pleroma.Web.Federator.publish(block))
  89. end
  90. end
  91. test "it blocks and does not federate if outgoing blocks are disabled", %{
  92. blocker: blocker,
  93. blocked: blocked
  94. } do
  95. clear_config([:instance, :federating], true)
  96. clear_config([:activitypub, :outgoing_blocks], false)
  97. with_mock Pleroma.Web.Federator,
  98. publish: fn _ -> nil end do
  99. assert {:ok, block} = CommonAPI.block(blocked, blocker)
  100. assert block.local
  101. assert User.blocks?(blocker, blocked)
  102. refute User.following?(blocker, blocked)
  103. refute User.following?(blocked, blocker)
  104. refute called(Pleroma.Web.Federator.publish(block))
  105. end
  106. end
  107. end
  108. describe "posting chat messages" do
  109. setup do: clear_config([:instance, :chat_limit])
  110. test "it posts a self-chat" do
  111. author = insert(:user)
  112. recipient = author
  113. {:ok, activity} =
  114. CommonAPI.post_chat_message(
  115. author,
  116. recipient,
  117. "remember to buy milk when milk truk arive"
  118. )
  119. assert activity.data["type"] == "Create"
  120. end
  121. test "it posts a chat message without content but with an attachment" do
  122. author = insert(:user)
  123. recipient = insert(:user)
  124. file = %Plug.Upload{
  125. content_type: "image/jpeg",
  126. path: Path.absname("test/fixtures/image.jpg"),
  127. filename: "an_image.jpg"
  128. }
  129. {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
  130. with_mocks([
  131. {
  132. Pleroma.Web.Streamer,
  133. [],
  134. [
  135. stream: fn _, _ ->
  136. nil
  137. end
  138. ]
  139. },
  140. {
  141. Pleroma.Web.Push,
  142. [],
  143. [
  144. send: fn _ -> nil end
  145. ]
  146. }
  147. ]) do
  148. {:ok, activity} =
  149. CommonAPI.post_chat_message(
  150. author,
  151. recipient,
  152. nil,
  153. media_id: upload.id
  154. )
  155. notification =
  156. Notification.for_user_and_activity(recipient, activity)
  157. |> Repo.preload(:activity)
  158. assert called(Pleroma.Web.Push.send(notification))
  159. assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
  160. assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
  161. assert activity
  162. end
  163. end
  164. test "it adds html newlines" do
  165. author = insert(:user)
  166. recipient = insert(:user)
  167. other_user = insert(:user)
  168. {:ok, activity} =
  169. CommonAPI.post_chat_message(
  170. author,
  171. recipient,
  172. "uguu\nuguuu"
  173. )
  174. assert other_user.ap_id not in activity.recipients
  175. object = Object.normalize(activity, fetch: false)
  176. assert object.data["content"] == "uguu<br/>uguuu"
  177. end
  178. test "it linkifies" do
  179. author = insert(:user)
  180. recipient = insert(:user)
  181. other_user = insert(:user)
  182. {:ok, activity} =
  183. CommonAPI.post_chat_message(
  184. author,
  185. recipient,
  186. "https://example.org is the site of @#{other_user.nickname} #2hu"
  187. )
  188. assert other_user.ap_id not in activity.recipients
  189. object = Object.normalize(activity, fetch: false)
  190. assert object.data["content"] ==
  191. "<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{other_user.id}\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
  192. end
  193. test "it posts a chat message" do
  194. author = insert(:user)
  195. recipient = insert(:user)
  196. {:ok, activity} =
  197. CommonAPI.post_chat_message(
  198. author,
  199. recipient,
  200. "a test message <script>alert('uuu')</script> :firefox:"
  201. )
  202. assert activity.data["type"] == "Create"
  203. assert activity.local
  204. object = Object.normalize(activity, fetch: false)
  205. assert object.data["type"] == "ChatMessage"
  206. assert object.data["to"] == [recipient.ap_id]
  207. assert object.data["content"] ==
  208. "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
  209. assert object.data["emoji"] == %{
  210. "firefox" => "http://localhost:4001/emoji/Firefox.gif"
  211. }
  212. assert Chat.get(author.id, recipient.ap_id)
  213. assert Chat.get(recipient.id, author.ap_id)
  214. assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
  215. end
  216. test "it reject messages over the local limit" do
  217. clear_config([:instance, :chat_limit], 2)
  218. author = insert(:user)
  219. recipient = insert(:user)
  220. {:error, message} =
  221. CommonAPI.post_chat_message(
  222. author,
  223. recipient,
  224. "123"
  225. )
  226. assert message == :content_too_long
  227. end
  228. test "it reject messages via MRF" do
  229. clear_config([:mrf_keyword, :reject], ["GNO"])
  230. clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
  231. author = insert(:user)
  232. recipient = insert(:user)
  233. assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
  234. CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
  235. end
  236. test "it reject messages with attachments not belonging to user" do
  237. author = insert(:user)
  238. not_author = insert(:user)
  239. recipient = author
  240. attachment = insert(:attachment, %{user: not_author})
  241. {:error, message} =
  242. CommonAPI.post_chat_message(
  243. author,
  244. recipient,
  245. "123",
  246. media_id: attachment.id
  247. )
  248. assert message == :forbidden
  249. end
  250. end
  251. describe "unblocking" do
  252. test "it works even without an existing block activity" do
  253. blocked = insert(:user)
  254. blocker = insert(:user)
  255. User.block(blocker, blocked)
  256. assert User.blocks?(blocker, blocked)
  257. assert {:ok, :no_activity} == CommonAPI.unblock(blocked, blocker)
  258. refute User.blocks?(blocker, blocked)
  259. end
  260. end
  261. describe "deletion" do
  262. test "it works with pruned objects" do
  263. user = insert(:user)
  264. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  265. clear_config([:instance, :federating], true)
  266. Object.normalize(post, fetch: false)
  267. |> Object.prune()
  268. with_mock Pleroma.Web.Federator,
  269. publish: fn _ -> nil end do
  270. assert {:ok, delete} = CommonAPI.delete(post.id, user)
  271. assert delete.local
  272. assert called(Pleroma.Web.Federator.publish(delete))
  273. end
  274. refute Activity.get_by_id(post.id)
  275. end
  276. test "it allows users to delete their posts" do
  277. user = insert(:user)
  278. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  279. clear_config([:instance, :federating], true)
  280. with_mock Pleroma.Web.Federator,
  281. publish: fn _ -> nil end do
  282. assert {:ok, delete} = CommonAPI.delete(post.id, user)
  283. assert delete.local
  284. assert called(Pleroma.Web.Federator.publish(delete))
  285. end
  286. refute Activity.get_by_id(post.id)
  287. end
  288. test "it does not allow a user to delete posts from another user" do
  289. user = insert(:user)
  290. other_user = insert(:user)
  291. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  292. assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
  293. assert Activity.get_by_id(post.id)
  294. end
  295. test "it allows privileged users to delete other user's posts" do
  296. clear_config([:instance, :moderator_privileges], [:messages_delete])
  297. user = insert(:user)
  298. moderator = insert(:user, is_moderator: true)
  299. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  300. assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
  301. assert delete.local
  302. refute Activity.get_by_id(post.id)
  303. end
  304. test "it doesn't allow unprivileged mods or admins to delete other user's posts" do
  305. clear_config([:instance, :admin_privileges], [])
  306. clear_config([:instance, :moderator_privileges], [])
  307. user = insert(:user)
  308. moderator = insert(:user, is_moderator: true, is_admin: true)
  309. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  310. assert {:error, "Could not delete"} = CommonAPI.delete(post.id, moderator)
  311. assert Activity.get_by_id(post.id)
  312. end
  313. test "privileged users deleting non-local posts won't federate the delete" do
  314. clear_config([:instance, :admin_privileges], [:messages_delete])
  315. # This is the user of the ingested activity
  316. _user =
  317. insert(:user,
  318. local: false,
  319. ap_id: "http://mastodon.example.org/users/admin",
  320. last_refreshed_at: NaiveDateTime.utc_now()
  321. )
  322. admin = insert(:user, is_admin: true)
  323. data =
  324. File.read!("test/fixtures/mastodon-post-activity.json")
  325. |> Jason.decode!()
  326. {:ok, post} = Transmogrifier.handle_incoming(data)
  327. with_mock Pleroma.Web.Federator,
  328. publish: fn _ -> nil end do
  329. assert {:ok, delete} = CommonAPI.delete(post.id, admin)
  330. assert delete.local
  331. refute called(Pleroma.Web.Federator.publish(:_))
  332. end
  333. refute Activity.get_by_id(post.id)
  334. end
  335. test "it allows privileged users to delete banned user's posts" do
  336. clear_config([:instance, :moderator_privileges], [:messages_delete])
  337. user = insert(:user)
  338. moderator = insert(:user, is_moderator: true)
  339. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  340. User.set_activation(user, false)
  341. assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
  342. assert delete.local
  343. refute Activity.get_by_id(post.id)
  344. end
  345. end
  346. test "favoriting race condition" do
  347. user = insert(:user)
  348. users_serial = insert_list(10, :user)
  349. users = insert_list(10, :user)
  350. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  351. users_serial
  352. |> Enum.map(fn user ->
  353. CommonAPI.favorite(activity.id, user)
  354. end)
  355. object = Object.get_by_ap_id(activity.data["object"])
  356. assert object.data["like_count"] == 10
  357. users
  358. |> Enum.map(fn user ->
  359. Task.async(fn ->
  360. CommonAPI.favorite(activity.id, user)
  361. end)
  362. end)
  363. |> Enum.map(&Task.await/1)
  364. object = Object.get_by_ap_id(activity.data["object"])
  365. assert object.data["like_count"] == 20
  366. end
  367. test "repeating race condition" do
  368. user = insert(:user)
  369. users_serial = insert_list(10, :user)
  370. users = insert_list(10, :user)
  371. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  372. users_serial
  373. |> Enum.map(fn user ->
  374. CommonAPI.repeat(activity.id, user)
  375. end)
  376. object = Object.get_by_ap_id(activity.data["object"])
  377. assert object.data["announcement_count"] == 10
  378. users
  379. |> Enum.map(fn user ->
  380. Task.async(fn ->
  381. CommonAPI.repeat(activity.id, user)
  382. end)
  383. end)
  384. |> Enum.map(&Task.await/1)
  385. object = Object.get_by_ap_id(activity.data["object"])
  386. assert object.data["announcement_count"] == 20
  387. end
  388. test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
  389. user = insert(:user)
  390. {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  391. [participation] = Participation.for_user(user)
  392. {:ok, convo_reply} =
  393. CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
  394. assert Visibility.direct?(convo_reply)
  395. assert activity.data["context"] == convo_reply.data["context"]
  396. end
  397. test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
  398. har = insert(:user)
  399. jafnhar = insert(:user)
  400. tridi = insert(:user)
  401. {:ok, activity} =
  402. CommonAPI.post(har, %{
  403. status: "@#{jafnhar.nickname} hey",
  404. visibility: "direct"
  405. })
  406. assert har.ap_id in activity.recipients
  407. assert jafnhar.ap_id in activity.recipients
  408. [participation] = Participation.for_user(har)
  409. {:ok, activity} =
  410. CommonAPI.post(har, %{
  411. status: "I don't really like @#{tridi.nickname}",
  412. visibility: "direct",
  413. in_reply_to_status_id: activity.id,
  414. in_reply_to_conversation_id: participation.id
  415. })
  416. assert har.ap_id in activity.recipients
  417. assert jafnhar.ap_id in activity.recipients
  418. refute tridi.ap_id in activity.recipients
  419. end
  420. test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
  421. har = insert(:user)
  422. jafnhar = insert(:user)
  423. tridi = insert(:user)
  424. clear_config([:instance, :safe_dm_mentions], true)
  425. {:ok, activity} =
  426. CommonAPI.post(har, %{
  427. status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
  428. visibility: "direct"
  429. })
  430. refute tridi.ap_id in activity.recipients
  431. assert jafnhar.ap_id in activity.recipients
  432. end
  433. test "it de-duplicates tags" do
  434. user = insert(:user)
  435. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
  436. object = Object.normalize(activity, fetch: false)
  437. assert Object.tags(object) == ["2hu"]
  438. end
  439. test "zwnj is treated as word character" do
  440. user = insert(:user)
  441. {:ok, activity} = CommonAPI.post(user, %{status: "#ساٴين‌س"})
  442. object = Object.normalize(activity, fetch: false)
  443. assert Object.tags(object) == ["ساٴين‌س"]
  444. end
  445. test "allows lang attribute" do
  446. user = insert(:user)
  447. text = ~s{<span lang="en">something</span><p lang="diaetuitech_rpyhpgc">random</p>}
  448. {:ok, activity} = CommonAPI.post(user, %{status: text, content_type: "text/html"})
  449. object = Object.normalize(activity, fetch: false)
  450. assert object.data["content"] == text
  451. end
  452. test "double dot in link is allowed" do
  453. user = insert(:user)
  454. text = "https://example.to/something..mp3"
  455. {:ok, activity} = CommonAPI.post(user, %{status: text})
  456. object = Object.normalize(activity, fetch: false)
  457. assert object.data["content"] == "<a href=\"#{text}\" rel=\"ugc\">#{text}</a>"
  458. end
  459. test "it adds emoji in the object" do
  460. user = insert(:user)
  461. {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
  462. assert Object.normalize(activity, fetch: false).data["emoji"]["firefox"]
  463. end
  464. describe "posting" do
  465. test "it adds an emoji on an external site" do
  466. user = insert(:user)
  467. {:ok, activity} = CommonAPI.post(user, %{status: "hey :external_emoji:"})
  468. assert %{"external_emoji" => url} = Object.normalize(activity).data["emoji"]
  469. assert url == "https://example.com/emoji.png"
  470. {:ok, activity} = CommonAPI.post(user, %{status: "hey :blank:"})
  471. assert %{"blank" => url} = Object.normalize(activity).data["emoji"]
  472. assert url == "#{Pleroma.Web.Endpoint.url()}/emoji/blank.png"
  473. end
  474. test "it copies emoji from the subject of the parent post" do
  475. %Object{} =
  476. object =
  477. Object.normalize("https://patch.cx/objects/a399c28e-c821-4820-bc3e-4afeb044c16f",
  478. fetch: true
  479. )
  480. activity = Activity.get_create_by_object_ap_id(object.data["id"])
  481. user = insert(:user)
  482. {:ok, reply_activity} =
  483. CommonAPI.post(user, %{
  484. in_reply_to_id: activity.id,
  485. status: ":joker_disapprove:",
  486. spoiler_text: ":joker_smile:"
  487. })
  488. assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
  489. refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
  490. end
  491. test "deactivated users can't post" do
  492. user = insert(:user, is_active: false)
  493. assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
  494. end
  495. test "it supports explicit addressing" do
  496. user = insert(:user)
  497. user_two = insert(:user)
  498. user_three = insert(:user)
  499. user_four = insert(:user)
  500. {:ok, activity} =
  501. CommonAPI.post(user, %{
  502. status:
  503. "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
  504. to: [user_two.nickname, user_four.nickname, "nonexistent"]
  505. })
  506. assert user.ap_id in activity.recipients
  507. assert user_two.ap_id in activity.recipients
  508. assert user_four.ap_id in activity.recipients
  509. refute user_three.ap_id in activity.recipients
  510. end
  511. test "it filters out obviously bad tags when accepting a post as HTML" do
  512. user = insert(:user)
  513. post = "<p><b>2hu</b></p><script>alert('xss')</script>"
  514. {:ok, activity} =
  515. CommonAPI.post(user, %{
  516. status: post,
  517. content_type: "text/html"
  518. })
  519. object = Object.normalize(activity, fetch: false)
  520. assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
  521. assert object.data["source"]["content"] == post
  522. end
  523. test "it filters out obviously bad tags when accepting a post as Markdown" do
  524. user = insert(:user)
  525. post = "<p><b>2hu</b></p><script>alert('xss')</script>"
  526. {:ok, activity} =
  527. CommonAPI.post(user, %{
  528. status: post,
  529. content_type: "text/markdown"
  530. })
  531. object = Object.normalize(activity, fetch: false)
  532. assert object.data["content"] == "<p><b>2hu</b></p>"
  533. assert object.data["source"]["content"] == post
  534. end
  535. test "it does not allow replies to direct messages that are not direct messages themselves" do
  536. user = insert(:user)
  537. {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
  538. assert {:ok, _} =
  539. CommonAPI.post(user, %{
  540. status: "suya..",
  541. visibility: "direct",
  542. in_reply_to_status_id: activity.id
  543. })
  544. Enum.each(["public", "private", "unlisted"], fn visibility ->
  545. assert {:error, "The message visibility must be direct"} =
  546. CommonAPI.post(user, %{
  547. status: "suya..",
  548. visibility: visibility,
  549. in_reply_to_status_id: activity.id
  550. })
  551. end)
  552. end
  553. test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
  554. user = insert(:user)
  555. other_user = insert(:user)
  556. third_user = insert(:user)
  557. {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
  558. {:ok, open_answer} =
  559. CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
  560. # The OP is implicitly added
  561. assert user.ap_id in open_answer.recipients
  562. {:ok, secret_answer} =
  563. CommonAPI.post(other_user, %{
  564. status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
  565. in_reply_to_status_id: post.id,
  566. visibility: "direct"
  567. })
  568. assert third_user.ap_id in secret_answer.recipients
  569. # The OP is not added
  570. refute user.ap_id in secret_answer.recipients
  571. end
  572. test "it allows to address a list" do
  573. user = insert(:user)
  574. {:ok, list} = Pleroma.List.create("foo", user)
  575. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
  576. assert activity.data["bcc"] == [list.ap_id]
  577. assert activity.recipients == [list.ap_id, user.ap_id]
  578. assert activity.data["listMessage"] == list.ap_id
  579. end
  580. test "it returns error when status is empty and no attachments" do
  581. user = insert(:user)
  582. assert {:error, "Cannot post an empty status without attachments"} =
  583. CommonAPI.post(user, %{status: ""})
  584. end
  585. test "it validates character limits are correctly enforced" do
  586. clear_config([:instance, :limit], 5)
  587. user = insert(:user)
  588. assert {:error, "The status is over the character limit"} =
  589. CommonAPI.post(user, %{status: "foobar"})
  590. assert {:ok, _activity} = CommonAPI.post(user, %{status: "12345"})
  591. end
  592. test "it validates media attachment limits are correctly enforced" do
  593. clear_config([:instance, :max_media_attachments], 4)
  594. user = insert(:user)
  595. file = %Plug.Upload{
  596. content_type: "image/jpeg",
  597. path: Path.absname("test/fixtures/image.jpg"),
  598. filename: "an_image.jpg"
  599. }
  600. {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
  601. assert {:error, "Too many attachments"} =
  602. CommonAPI.post(user, %{
  603. status: "",
  604. media_ids: List.duplicate(upload.id, 5)
  605. })
  606. assert {:ok, _activity} =
  607. CommonAPI.post(user, %{
  608. status: "",
  609. media_ids: [upload.id]
  610. })
  611. end
  612. test "it can handle activities that expire" do
  613. user = insert(:user)
  614. expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
  615. assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
  616. assert_enqueued(
  617. worker: Pleroma.Workers.PurgeExpiredActivity,
  618. args: %{activity_id: activity.id},
  619. scheduled_at: expires_at
  620. )
  621. end
  622. test "it allows quote posting" do
  623. user = insert(:user)
  624. {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
  625. {:ok, quote_post} = CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id})
  626. quoted = Object.normalize(quoted)
  627. quote_post = Object.normalize(quote_post)
  628. assert quote_post.data["quoteUrl"] == quoted.data["id"]
  629. # The OP is not mentioned
  630. refute quoted.data["actor"] in quote_post.data["to"]
  631. end
  632. test "quote posting with explicit addressing doesn't mention the OP" do
  633. user = insert(:user)
  634. {:ok, quoted} = CommonAPI.post(user, %{status: "Hello world"})
  635. {:ok, quote_post} =
  636. CommonAPI.post(user, %{status: "nice post", quote_id: quoted.id, to: []})
  637. assert Object.normalize(quote_post).data["to"] == [Pleroma.Constants.as_public()]
  638. end
  639. test "quote posting visibility" do
  640. user = insert(:user)
  641. another_user = insert(:user)
  642. {:ok, direct} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  643. {:ok, private} = CommonAPI.post(user, %{status: ".", visibility: "private"})
  644. {:ok, unlisted} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
  645. {:ok, local} = CommonAPI.post(user, %{status: ".", visibility: "local"})
  646. {:ok, public} = CommonAPI.post(user, %{status: ".", visibility: "public"})
  647. {:error, _} = CommonAPI.post(user, %{status: "nice", quote_id: direct.id})
  648. {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: private.id})
  649. {:error, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: private.id})
  650. {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: unlisted.id})
  651. {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: unlisted.id})
  652. {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: local.id})
  653. {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: local.id})
  654. {:ok, _} = CommonAPI.post(user, %{status: "nice", quote_id: public.id})
  655. {:ok, _} = CommonAPI.post(another_user, %{status: "nice", quote_id: public.id})
  656. end
  657. test "it properly mentions punycode domain" do
  658. user = insert(:user)
  659. _mentioned_user =
  660. insert(:user, ap_id: "https://xn--i2raa.com/users/yyy", nickname: "yyy@xn--i2raa.com")
  661. {:ok, activity} =
  662. CommonAPI.post(user, %{status: "hey @yyy@xn--i2raa.com", content_type: "text/markdown"})
  663. assert "https://xn--i2raa.com/users/yyy" in Object.normalize(activity).data["to"]
  664. end
  665. end
  666. describe "reactions" do
  667. test "reacting to a status with an emoji" do
  668. user = insert(:user)
  669. other_user = insert(:user)
  670. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  671. {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  672. assert reaction.data["actor"] == user.ap_id
  673. assert reaction.data["content"] == "👍"
  674. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  675. {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
  676. end
  677. test "unreacting to a status with an emoji" do
  678. user = insert(:user)
  679. other_user = insert(:user)
  680. clear_config([:instance, :federating], true)
  681. with_mock Pleroma.Web.Federator,
  682. publish: fn _ -> nil end do
  683. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  684. {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  685. {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
  686. assert unreaction.data["type"] == "Undo"
  687. assert unreaction.data["object"] == reaction.data["id"]
  688. assert unreaction.local
  689. # On federation, it contains the undone (and deleted) object
  690. unreaction_with_object = %{
  691. unreaction
  692. | data: Map.put(unreaction.data, "object", reaction.data)
  693. }
  694. assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
  695. end
  696. end
  697. test "repeating a status" do
  698. user = insert(:user)
  699. other_user = insert(:user)
  700. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  701. {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
  702. assert Visibility.public?(announce_activity)
  703. end
  704. test "can't repeat a repeat" do
  705. user = insert(:user)
  706. other_user = insert(:user)
  707. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  708. {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
  709. refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
  710. end
  711. test "repeating a status privately" do
  712. user = insert(:user)
  713. other_user = insert(:user)
  714. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  715. {:ok, %Activity{} = announce_activity} =
  716. CommonAPI.repeat(activity.id, user, %{visibility: "private"})
  717. assert Visibility.private?(announce_activity)
  718. refute Visibility.visible_for_user?(announce_activity, nil)
  719. end
  720. test "author can repeat own private statuses" do
  721. author = insert(:user)
  722. follower = insert(:user)
  723. CommonAPI.follow(author, follower)
  724. {:ok, activity} = CommonAPI.post(author, %{status: "cofe", visibility: "private"})
  725. {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, author)
  726. assert Visibility.private?(announce_activity)
  727. refute Visibility.visible_for_user?(announce_activity, nil)
  728. assert Visibility.visible_for_user?(activity, follower)
  729. assert {:error, :not_found} = CommonAPI.repeat(activity.id, follower)
  730. end
  731. test "favoriting a status" do
  732. user = insert(:user)
  733. other_user = insert(:user)
  734. {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
  735. {:ok, %Activity{data: data}} = CommonAPI.favorite(post_activity.id, user)
  736. assert data["type"] == "Like"
  737. assert data["actor"] == user.ap_id
  738. assert data["object"] == post_activity.data["object"]
  739. end
  740. test "retweeting a status twice returns the status" do
  741. user = insert(:user)
  742. other_user = insert(:user)
  743. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  744. {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
  745. {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
  746. end
  747. test "favoriting a status twice returns ok, but without the like activity" do
  748. user = insert(:user)
  749. other_user = insert(:user)
  750. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  751. {:ok, %Activity{}} = CommonAPI.favorite(activity.id, user)
  752. assert {:ok, :already_liked} = CommonAPI.favorite(activity.id, user)
  753. end
  754. end
  755. describe "pinned statuses" do
  756. setup do
  757. clear_config([:instance, :max_pinned_statuses], 1)
  758. user = insert(:user)
  759. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
  760. [user: user, activity: activity]
  761. end
  762. test "activity not found error", %{user: user} do
  763. assert {:error, :not_found} = CommonAPI.pin("id", user)
  764. end
  765. test "pin status", %{user: user, activity: activity} do
  766. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  767. %{data: %{"id" => object_id}} = Object.normalize(activity)
  768. user = refresh_record(user)
  769. assert user.pinned_objects |> Map.keys() == [object_id]
  770. end
  771. test "pin poll", %{user: user} do
  772. {:ok, activity} =
  773. CommonAPI.post(user, %{
  774. status: "How is fediverse today?",
  775. poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
  776. })
  777. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  778. %{data: %{"id" => object_id}} = Object.normalize(activity)
  779. user = refresh_record(user)
  780. assert user.pinned_objects |> Map.keys() == [object_id]
  781. end
  782. test "unlisted statuses can be pinned", %{user: user} do
  783. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
  784. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  785. end
  786. test "only self-authored can be pinned", %{activity: activity} do
  787. user = insert(:user)
  788. assert {:error, :ownership_error} = CommonAPI.pin(activity.id, user)
  789. end
  790. test "max pinned statuses", %{user: user, activity: activity_one} do
  791. {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
  792. assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
  793. user = refresh_record(user)
  794. assert {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity_two.id, user)
  795. end
  796. test "only public can be pinned", %{user: user} do
  797. {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"})
  798. {:error, :visibility_error} = CommonAPI.pin(activity.id, user)
  799. end
  800. test "unpin status", %{user: user, activity: activity} do
  801. {:ok, activity} = CommonAPI.pin(activity.id, user)
  802. user = refresh_record(user)
  803. id = activity.id
  804. assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
  805. user = refresh_record(user)
  806. assert user.pinned_objects == %{}
  807. end
  808. test "should unpin when deleting a status", %{user: user, activity: activity} do
  809. {:ok, activity} = CommonAPI.pin(activity.id, user)
  810. user = refresh_record(user)
  811. assert {:ok, _} = CommonAPI.delete(activity.id, user)
  812. user = refresh_record(user)
  813. assert user.pinned_objects == %{}
  814. end
  815. test "ephemeral activity won't be deleted if was pinned", %{user: user} do
  816. {:ok, activity} = CommonAPI.post(user, %{status: "Hello!", expires_in: 601})
  817. assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
  818. {:ok, _activity} = CommonAPI.pin(activity.id, user)
  819. refute Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
  820. user = refresh_record(user)
  821. {:ok, _} = CommonAPI.unpin(activity.id, user)
  822. # recreates expiration job on unpin
  823. assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
  824. end
  825. test "ephemeral activity deletion job won't be deleted on pinning error", %{
  826. user: user,
  827. activity: activity
  828. } do
  829. clear_config([:instance, :max_pinned_statuses], 1)
  830. {:ok, _activity} = CommonAPI.pin(activity.id, user)
  831. {:ok, activity2} = CommonAPI.post(user, %{status: "another status", expires_in: 601})
  832. assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
  833. user = refresh_record(user)
  834. {:error, :pinned_statuses_limit_reached} = CommonAPI.pin(activity2.id, user)
  835. assert Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity2.id)
  836. end
  837. end
  838. describe "mute tests" do
  839. setup do
  840. user = insert(:user)
  841. activity = insert(:note_activity)
  842. [user: user, activity: activity]
  843. end
  844. test "marks notifications as read after mute" do
  845. author = insert(:user)
  846. activity = insert(:note_activity, user: author)
  847. friend1 = insert(:user)
  848. friend2 = insert(:user)
  849. {:ok, reply_activity} =
  850. CommonAPI.post(
  851. friend2,
  852. %{
  853. status: "@#{author.nickname} @#{friend1.nickname} test reply",
  854. in_reply_to_status_id: activity.id
  855. }
  856. )
  857. {:ok, favorite_activity} = CommonAPI.favorite(activity.id, friend2)
  858. {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
  859. assert Repo.aggregate(
  860. from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
  861. :count
  862. ) == 1
  863. unread_notifications =
  864. Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
  865. assert Enum.any?(unread_notifications, fn n ->
  866. n.type == "favourite" && n.activity_id == favorite_activity.id
  867. end)
  868. assert Enum.any?(unread_notifications, fn n ->
  869. n.type == "reblog" && n.activity_id == repeat_activity.id
  870. end)
  871. assert Enum.any?(unread_notifications, fn n ->
  872. n.type == "mention" && n.activity_id == reply_activity.id
  873. end)
  874. {:ok, _} = CommonAPI.add_mute(activity, author)
  875. assert CommonAPI.thread_muted?(activity, author)
  876. assert Repo.aggregate(
  877. from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
  878. :count
  879. ) == 1
  880. read_notifications =
  881. Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
  882. assert Enum.any?(read_notifications, fn n ->
  883. n.type == "favourite" && n.activity_id == favorite_activity.id
  884. end)
  885. assert Enum.any?(read_notifications, fn n ->
  886. n.type == "reblog" && n.activity_id == repeat_activity.id
  887. end)
  888. assert Enum.any?(read_notifications, fn n ->
  889. n.type == "mention" && n.activity_id == reply_activity.id
  890. end)
  891. end
  892. test "add mute", %{user: user, activity: activity} do
  893. {:ok, _} = CommonAPI.add_mute(activity, user)
  894. assert CommonAPI.thread_muted?(activity, user)
  895. end
  896. test "add expiring mute", %{user: user, activity: activity} do
  897. {:ok, _} = CommonAPI.add_mute(activity, user, %{expires_in: 60})
  898. assert CommonAPI.thread_muted?(activity, user)
  899. worker = Pleroma.Workers.MuteExpireWorker
  900. args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
  901. assert_enqueued(
  902. worker: worker,
  903. args: args
  904. )
  905. assert :ok = perform_job(worker, args)
  906. refute CommonAPI.thread_muted?(activity, user)
  907. end
  908. test "remove mute", %{user: user, activity: activity} do
  909. CommonAPI.add_mute(activity, user)
  910. {:ok, _} = CommonAPI.remove_mute(activity, user)
  911. refute CommonAPI.thread_muted?(activity, user)
  912. end
  913. test "remove mute by ids", %{user: user, activity: activity} do
  914. CommonAPI.add_mute(activity, user)
  915. {:ok, _} = CommonAPI.remove_mute(activity.id, user.id)
  916. refute CommonAPI.thread_muted?(activity, user)
  917. end
  918. test "check that mutes can't be duplicate", %{user: user, activity: activity} do
  919. CommonAPI.add_mute(activity, user)
  920. {:error, _} = CommonAPI.add_mute(activity, user)
  921. end
  922. end
  923. describe "reports" do
  924. test "creates a report" do
  925. reporter = insert(:user)
  926. target_user = insert(:user)
  927. {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
  928. activity = Activity.normalize(activity)
  929. reporter_ap_id = reporter.ap_id
  930. target_ap_id = target_user.ap_id
  931. reported_object_ap_id = activity.object.data["id"]
  932. comment = "foobar"
  933. report_data = %{
  934. account_id: target_user.id,
  935. comment: comment,
  936. status_ids: [activity.id]
  937. }
  938. note_obj = %{
  939. "type" => "Note",
  940. "id" => reported_object_ap_id,
  941. "content" => "foobar",
  942. "published" => activity.object.data["published"],
  943. "actor" => AccountView.render("show.json", %{user: target_user})
  944. }
  945. assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
  946. assert %Activity{
  947. actor: ^reporter_ap_id,
  948. data: %{
  949. "type" => "Flag",
  950. "content" => ^comment,
  951. "object" => [^target_ap_id, ^note_obj],
  952. "state" => "open"
  953. }
  954. } = flag_activity
  955. end
  956. test "updates report state" do
  957. [reporter, target_user] = insert_pair(:user)
  958. activity = insert(:note_activity, user: target_user)
  959. object = Object.normalize(activity)
  960. {:ok, %Activity{id: report_id}} =
  961. CommonAPI.report(reporter, %{
  962. account_id: target_user.id,
  963. comment: "I feel offended",
  964. status_ids: [activity.id]
  965. })
  966. {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
  967. assert report.data["state"] == "resolved"
  968. [reported_user, object_id] = report.data["object"]
  969. assert reported_user == target_user.ap_id
  970. assert object_id == object.data["id"]
  971. end
  972. test "updates report state, don't strip when report_strip_status is false" do
  973. clear_config([:instance, :report_strip_status], false)
  974. [reporter, target_user] = insert_pair(:user)
  975. activity = insert(:note_activity, user: target_user)
  976. {:ok, %Activity{id: report_id, data: report_data}} =
  977. CommonAPI.report(reporter, %{
  978. account_id: target_user.id,
  979. comment: "I feel offended",
  980. status_ids: [activity.id]
  981. })
  982. {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
  983. assert report.data["state"] == "resolved"
  984. [reported_user, reported_activity] = report.data["object"]
  985. assert reported_user == target_user.ap_id
  986. assert is_map(reported_activity)
  987. assert reported_activity["content"] ==
  988. report_data["object"] |> Enum.at(1) |> Map.get("content")
  989. end
  990. test "does not update report state when state is unsupported" do
  991. [reporter, target_user] = insert_pair(:user)
  992. activity = insert(:note_activity, user: target_user)
  993. {:ok, %Activity{id: report_id}} =
  994. CommonAPI.report(reporter, %{
  995. account_id: target_user.id,
  996. comment: "I feel offended",
  997. status_ids: [activity.id]
  998. })
  999. assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
  1000. end
  1001. test "updates state of multiple reports" do
  1002. [reporter, target_user] = insert_pair(:user)
  1003. activity = insert(:note_activity, user: target_user)
  1004. {:ok, %Activity{id: first_report_id}} =
  1005. CommonAPI.report(reporter, %{
  1006. account_id: target_user.id,
  1007. comment: "I feel offended",
  1008. status_ids: [activity.id]
  1009. })
  1010. {:ok, %Activity{id: second_report_id}} =
  1011. CommonAPI.report(reporter, %{
  1012. account_id: target_user.id,
  1013. comment: "I feel very offended!",
  1014. status_ids: [activity.id]
  1015. })
  1016. {:ok, report_ids} =
  1017. CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
  1018. first_report = Activity.get_by_id(first_report_id)
  1019. second_report = Activity.get_by_id(second_report_id)
  1020. assert report_ids -- [first_report_id, second_report_id] == []
  1021. assert first_report.data["state"] == "resolved"
  1022. assert second_report.data["state"] == "resolved"
  1023. end
  1024. test "creates a report with provided rules" do
  1025. reporter = insert(:user)
  1026. target_user = insert(:user)
  1027. %{id: rule_id} = Rule.create(%{text: "There are no rules"})
  1028. reporter_ap_id = reporter.ap_id
  1029. target_ap_id = target_user.ap_id
  1030. report_data = %{
  1031. account_id: target_user.id,
  1032. rule_ids: [rule_id]
  1033. }
  1034. assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
  1035. assert %Activity{
  1036. actor: ^reporter_ap_id,
  1037. data: %{
  1038. "type" => "Flag",
  1039. "object" => [^target_ap_id],
  1040. "state" => "open",
  1041. "rules" => [^rule_id]
  1042. }
  1043. } = flag_activity
  1044. end
  1045. end
  1046. describe "reblog muting" do
  1047. setup do
  1048. muter = insert(:user)
  1049. muted = insert(:user)
  1050. [muter: muter, muted: muted]
  1051. end
  1052. test "add a reblog mute", %{muter: muter, muted: muted} do
  1053. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muted, muter)
  1054. assert User.showing_reblogs?(muter, muted) == false
  1055. end
  1056. test "remove a reblog mute", %{muter: muter, muted: muted} do
  1057. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muted, muter)
  1058. {:ok, _reblog_mute} = CommonAPI.show_reblogs(muted, muter)
  1059. assert User.showing_reblogs?(muter, muted) == true
  1060. end
  1061. end
  1062. describe "follow/2" do
  1063. test "directly follows a non-locked local user" do
  1064. [follower, followed] = insert_pair(:user)
  1065. {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
  1066. assert User.following?(follower, followed)
  1067. end
  1068. end
  1069. describe "unfollow/2" do
  1070. test "also unsubscribes a user" do
  1071. [follower, followed] = insert_pair(:user)
  1072. {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
  1073. {:ok, _subscription} = User.subscribe(follower, followed)
  1074. assert User.subscribed_to?(follower, followed)
  1075. {:ok, follower} = CommonAPI.unfollow(followed, follower)
  1076. refute User.subscribed_to?(follower, followed)
  1077. end
  1078. test "also unpins a user" do
  1079. [follower, followed] = insert_pair(:user)
  1080. {:ok, followed, follower, _} = CommonAPI.follow(followed, follower)
  1081. {:ok, _endorsement} = User.endorse(follower, followed)
  1082. assert User.endorses?(follower, followed)
  1083. {:ok, follower} = CommonAPI.unfollow(followed, follower)
  1084. refute User.endorses?(follower, followed)
  1085. end
  1086. test "cancels a pending follow for a local user" do
  1087. follower = insert(:user)
  1088. followed = insert(:user, is_locked: true)
  1089. assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} =
  1090. CommonAPI.follow(followed, follower)
  1091. assert User.get_follow_state(follower, followed) == :follow_pending
  1092. assert {:ok, follower} = CommonAPI.unfollow(followed, follower)
  1093. assert User.get_follow_state(follower, followed) == nil
  1094. assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
  1095. Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
  1096. assert %{
  1097. data: %{
  1098. "type" => "Undo",
  1099. "object" => %{"type" => "Follow", "state" => "cancelled"}
  1100. }
  1101. } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
  1102. end
  1103. test "cancels a pending follow for a remote user" do
  1104. follower = insert(:user)
  1105. followed = insert(:user, is_locked: true, local: false)
  1106. assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} =
  1107. CommonAPI.follow(followed, follower)
  1108. assert User.get_follow_state(follower, followed) == :follow_pending
  1109. assert {:ok, follower} = CommonAPI.unfollow(followed, follower)
  1110. assert User.get_follow_state(follower, followed) == nil
  1111. assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
  1112. Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
  1113. assert %{
  1114. data: %{
  1115. "type" => "Undo",
  1116. "object" => %{"type" => "Follow", "state" => "cancelled"}
  1117. }
  1118. } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
  1119. end
  1120. end
  1121. describe "accept_follow_request/2" do
  1122. test "after acceptance, it sets all existing pending follow request states to 'accept'" do
  1123. user = insert(:user, is_locked: true)
  1124. follower = insert(:user)
  1125. follower_two = insert(:user)
  1126. {:ok, _, _, follow_activity} = CommonAPI.follow(user, follower)
  1127. {:ok, _, _, follow_activity_two} = CommonAPI.follow(user, follower)
  1128. {:ok, _, _, follow_activity_three} = CommonAPI.follow(user, follower_two)
  1129. assert follow_activity.data["state"] == "pending"
  1130. assert follow_activity_two.data["state"] == "pending"
  1131. assert follow_activity_three.data["state"] == "pending"
  1132. {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
  1133. assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
  1134. assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
  1135. assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
  1136. end
  1137. test "after rejection, it sets all existing pending follow request states to 'reject'" do
  1138. user = insert(:user, is_locked: true)
  1139. follower = insert(:user)
  1140. follower_two = insert(:user)
  1141. {:ok, _, _, follow_activity} = CommonAPI.follow(user, follower)
  1142. {:ok, _, _, follow_activity_two} = CommonAPI.follow(user, follower)
  1143. {:ok, _, _, follow_activity_three} = CommonAPI.follow(user, follower_two)
  1144. assert follow_activity.data["state"] == "pending"
  1145. assert follow_activity_two.data["state"] == "pending"
  1146. assert follow_activity_three.data["state"] == "pending"
  1147. {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
  1148. assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
  1149. assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
  1150. assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
  1151. end
  1152. test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
  1153. user = insert(:user, is_locked: true)
  1154. not_follower = insert(:user)
  1155. CommonAPI.accept_follow_request(not_follower, user)
  1156. assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
  1157. end
  1158. end
  1159. describe "vote/3" do
  1160. test "does not allow to vote twice" do
  1161. user = insert(:user)
  1162. other_user = insert(:user)
  1163. {:ok, activity} =
  1164. CommonAPI.post(user, %{
  1165. status: "Am I cute?",
  1166. poll: %{options: ["Yes", "No"], expires_in: 20}
  1167. })
  1168. object = Object.normalize(activity, fetch: false)
  1169. {:ok, _, object} = CommonAPI.vote(object, other_user, [0])
  1170. assert {:error, "Already voted"} == CommonAPI.vote(object, other_user, [1])
  1171. end
  1172. end
  1173. describe "listen/2" do
  1174. test "returns a valid activity" do
  1175. user = insert(:user)
  1176. {:ok, activity} =
  1177. CommonAPI.listen(user, %{
  1178. title: "lain radio episode 1",
  1179. album: "lain radio",
  1180. artist: "lain",
  1181. length: 180_000
  1182. })
  1183. object = Object.normalize(activity, fetch: false)
  1184. assert object.data["title"] == "lain radio episode 1"
  1185. assert Visibility.get_visibility(activity) == "public"
  1186. end
  1187. test "respects visibility=private" do
  1188. user = insert(:user)
  1189. {:ok, activity} =
  1190. CommonAPI.listen(user, %{
  1191. title: "lain radio episode 1",
  1192. album: "lain radio",
  1193. artist: "lain",
  1194. length: 180_000,
  1195. visibility: "private"
  1196. })
  1197. object = Object.normalize(activity, fetch: false)
  1198. assert object.data["title"] == "lain radio episode 1"
  1199. assert Visibility.get_visibility(activity) == "private"
  1200. end
  1201. end
  1202. describe "get_user/1" do
  1203. test "gets user by ap_id" do
  1204. user = insert(:user)
  1205. assert CommonAPI.get_user(user.ap_id) == user
  1206. end
  1207. test "gets user by guessed nickname" do
  1208. user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
  1209. assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
  1210. end
  1211. test "fallback" do
  1212. assert %User{
  1213. name: "",
  1214. ap_id: "",
  1215. nickname: "erroruser@example.com"
  1216. } = CommonAPI.get_user("")
  1217. end
  1218. end
  1219. describe "with `local` visibility" do
  1220. setup do: clear_config([:instance, :federating], true)
  1221. test "post" do
  1222. user = insert(:user)
  1223. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1224. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  1225. assert Visibility.local_public?(activity)
  1226. assert_not_called(Pleroma.Web.Federator.publish(activity))
  1227. end
  1228. end
  1229. test "delete" do
  1230. user = insert(:user)
  1231. {:ok, %Activity{id: activity_id}} =
  1232. CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  1233. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1234. assert {:ok, %Activity{data: %{"deleted_activity_id" => ^activity_id}} = activity} =
  1235. CommonAPI.delete(activity_id, user)
  1236. assert Visibility.local_public?(activity)
  1237. assert_not_called(Pleroma.Web.Federator.publish(activity))
  1238. end
  1239. end
  1240. test "repeat" do
  1241. user = insert(:user)
  1242. other_user = insert(:user)
  1243. {:ok, %Activity{id: activity_id}} =
  1244. CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1245. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1246. assert {:ok, %Activity{data: %{"type" => "Announce"}} = activity} =
  1247. CommonAPI.repeat(activity_id, user)
  1248. assert Visibility.local_public?(activity)
  1249. refute called(Pleroma.Web.Federator.publish(activity))
  1250. end
  1251. end
  1252. test "unrepeat" do
  1253. user = insert(:user)
  1254. other_user = insert(:user)
  1255. {:ok, %Activity{id: activity_id}} =
  1256. CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1257. assert {:ok, _} = CommonAPI.repeat(activity_id, user)
  1258. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1259. assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
  1260. CommonAPI.unrepeat(activity_id, user)
  1261. assert Visibility.local_public?(activity)
  1262. refute called(Pleroma.Web.Federator.publish(activity))
  1263. end
  1264. end
  1265. test "favorite" do
  1266. user = insert(:user)
  1267. other_user = insert(:user)
  1268. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1269. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1270. assert {:ok, %Activity{data: %{"type" => "Like"}} = activity} =
  1271. CommonAPI.favorite(activity.id, user)
  1272. assert Visibility.local_public?(activity)
  1273. refute called(Pleroma.Web.Federator.publish(activity))
  1274. end
  1275. end
  1276. test "unfavorite" do
  1277. user = insert(:user)
  1278. other_user = insert(:user)
  1279. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1280. {:ok, %Activity{}} = CommonAPI.favorite(activity.id, user)
  1281. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1282. assert {:ok, activity} = CommonAPI.unfavorite(activity.id, user)
  1283. assert Visibility.local_public?(activity)
  1284. refute called(Pleroma.Web.Federator.publish(activity))
  1285. end
  1286. end
  1287. test "react_with_emoji" do
  1288. user = insert(:user)
  1289. other_user = insert(:user)
  1290. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1291. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1292. assert {:ok, %Activity{data: %{"type" => "EmojiReact"}} = activity} =
  1293. CommonAPI.react_with_emoji(activity.id, user, "👍")
  1294. assert Visibility.local_public?(activity)
  1295. refute called(Pleroma.Web.Federator.publish(activity))
  1296. end
  1297. end
  1298. test "unreact_with_emoji" do
  1299. user = insert(:user)
  1300. other_user = insert(:user)
  1301. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe", visibility: "local"})
  1302. {:ok, _reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  1303. with_mock Pleroma.Web.Federator, publish: fn _ -> :ok end do
  1304. assert {:ok, %Activity{data: %{"type" => "Undo"}} = activity} =
  1305. CommonAPI.unreact_with_emoji(activity.id, user, "👍")
  1306. assert Visibility.local_public?(activity)
  1307. refute called(Pleroma.Web.Federator.publish(activity))
  1308. end
  1309. end
  1310. end
  1311. describe "update/3" do
  1312. test "updates a post" do
  1313. user = insert(:user)
  1314. {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1"})
  1315. {:ok, updated} = CommonAPI.update(activity, user, %{status: "updated 2"})
  1316. updated_object = Object.normalize(updated)
  1317. assert updated_object.data["content"] == "updated 2"
  1318. assert Map.get(updated_object.data, "summary", "") == ""
  1319. assert Map.has_key?(updated_object.data, "updated")
  1320. end
  1321. test "does not change visibility" do
  1322. user = insert(:user)
  1323. {:ok, activity} =
  1324. CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1", visibility: "private"})
  1325. {:ok, updated} = CommonAPI.update(activity, user, %{status: "updated 2"})
  1326. updated_object = Object.normalize(updated)
  1327. assert updated_object.data["content"] == "updated 2"
  1328. assert Map.get(updated_object.data, "summary", "") == ""
  1329. assert Visibility.get_visibility(updated_object) == "private"
  1330. assert Visibility.get_visibility(updated) == "private"
  1331. end
  1332. test "updates a post with emoji" do
  1333. [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
  1334. user = insert(:user)
  1335. {:ok, activity} =
  1336. CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
  1337. {:ok, updated} = CommonAPI.update(activity, user, %{status: "updated 2 :#{emoji2}:"})
  1338. updated_object = Object.normalize(updated)
  1339. assert updated_object.data["content"] == "updated 2 :#{emoji2}:"
  1340. assert %{^emoji2 => _} = updated_object.data["emoji"]
  1341. end
  1342. test "updates a post with emoji and federate properly" do
  1343. [{emoji1, _}, {emoji2, _} | _] = Pleroma.Emoji.get_all()
  1344. user = insert(:user)
  1345. {:ok, activity} =
  1346. CommonAPI.post(user, %{status: "foo1", spoiler_text: "title 1 :#{emoji1}:"})
  1347. clear_config([:instance, :federating], true)
  1348. with_mock Pleroma.Web.Federator,
  1349. publish: fn _p -> nil end do
  1350. {:ok, updated} = CommonAPI.update(activity, user, %{status: "updated 2 :#{emoji2}:"})
  1351. assert updated.data["object"]["content"] == "updated 2 :#{emoji2}:"
  1352. assert %{^emoji2 => _} = updated.data["object"]["emoji"]
  1353. assert called(Pleroma.Web.Federator.publish(updated))
  1354. end
  1355. end
  1356. test "editing a post that copied a remote title with remote emoji should keep that emoji" do
  1357. remote_emoji_uri = "https://remote.org/emoji.png"
  1358. note =
  1359. insert(
  1360. :note,
  1361. data: %{
  1362. "summary" => ":remoteemoji:",
  1363. "emoji" => %{
  1364. "remoteemoji" => remote_emoji_uri
  1365. },
  1366. "tag" => [
  1367. %{
  1368. "type" => "Emoji",
  1369. "name" => "remoteemoji",
  1370. "icon" => %{"url" => remote_emoji_uri}
  1371. }
  1372. ]
  1373. }
  1374. )
  1375. note_activity = insert(:note_activity, note: note)
  1376. user = insert(:user)
  1377. {:ok, reply} =
  1378. CommonAPI.post(user, %{
  1379. status: "reply",
  1380. spoiler_text: ":remoteemoji:",
  1381. in_reply_to_id: note_activity.id
  1382. })
  1383. assert reply.object.data["emoji"]["remoteemoji"] == remote_emoji_uri
  1384. {:ok, edit} =
  1385. CommonAPI.update(reply, user, %{status: "reply mew mew", spoiler_text: ":remoteemoji:"})
  1386. edited_note = Pleroma.Object.normalize(edit)
  1387. assert edited_note.data["emoji"]["remoteemoji"] == remote_emoji_uri
  1388. end
  1389. test "respects MRF" do
  1390. user = insert(:user)
  1391. clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
  1392. clear_config([:mrf_keyword, :replace], [{"updated", "mewmew"}])
  1393. {:ok, activity} = CommonAPI.post(user, %{status: "foo1", spoiler_text: "updated 1"})
  1394. assert Object.normalize(activity).data["summary"] == "mewmew 1"
  1395. {:ok, updated} = CommonAPI.update(activity, user, %{status: "updated 2"})
  1396. updated_object = Object.normalize(updated)
  1397. assert updated_object.data["content"] == "mewmew 2"
  1398. assert Map.get(updated_object.data, "summary", "") == ""
  1399. assert Map.has_key?(updated_object.data, "updated")
  1400. end
  1401. end
  1402. describe "Group actors" do
  1403. setup do
  1404. poster = insert(:user)
  1405. group = insert(:user, actor_type: "Group")
  1406. other_group = insert(:user, actor_type: "Group")
  1407. %{poster: poster, group: group, other_group: other_group}
  1408. end
  1409. test "it boosts public posts", %{poster: poster, group: group} do
  1410. {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"})
  1411. announces = get_announces_of_object(post.object)
  1412. assert [_] = announces
  1413. end
  1414. test "it does not boost private posts", %{poster: poster, group: group} do
  1415. {:ok, private_post} =
  1416. CommonAPI.post(poster, %{status: "hey @#{group.nickname}", visibility: "private"})
  1417. assert [] = get_announces_of_object(private_post.object)
  1418. end
  1419. test "remote groups do not boost any posts", %{poster: poster} do
  1420. remote_group =
  1421. insert(:user, actor_type: "Group", local: false, nickname: "remote@example.com")
  1422. {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{User.full_nickname(remote_group)}"})
  1423. assert remote_group.ap_id in post.data["to"]
  1424. announces = get_announces_of_object(post.object)
  1425. assert [] = announces
  1426. end
  1427. test "multiple groups mentioned", %{poster: poster, group: group, other_group: other_group} do
  1428. {:ok, post} =
  1429. CommonAPI.post(poster, %{status: "hey @#{group.nickname} @#{other_group.nickname}"})
  1430. announces = get_announces_of_object(post.object)
  1431. assert [_, _] = announces
  1432. end
  1433. test "it does not boost if group is blocking poster", %{poster: poster, group: group} do
  1434. {:ok, _} = CommonAPI.block(poster, group)
  1435. {:ok, post} = CommonAPI.post(poster, %{status: "hey @#{group.nickname}"})
  1436. announces = get_announces_of_object(post.object)
  1437. assert [] = announces
  1438. end
  1439. end
  1440. describe "Oban jobs are cancelled" do
  1441. setup do
  1442. clear_config([:instance, :federating], true)
  1443. local_user = insert(:user)
  1444. remote_one =
  1445. insert(:user, %{
  1446. local: false,
  1447. nickname: "nick1@domain.com",
  1448. ap_id: "https://domain.com/users/nick1",
  1449. inbox: "https://domain.com/users/nick1/inbox",
  1450. shared_inbox: "https://domain.com/inbox"
  1451. })
  1452. remote_two =
  1453. insert(:user, %{
  1454. local: false,
  1455. nickname: "nick2@example.com",
  1456. ap_id: "https://example.com/users/nick2",
  1457. inbox: "https://example.com/users/nick2/inbox",
  1458. shared_inbox: "https://example.com/inbox"
  1459. })
  1460. %{local_user: local_user, remote_one: remote_one, remote_two: remote_two}
  1461. end
  1462. test "when deleting posts", %{
  1463. local_user: local_user,
  1464. remote_one: remote_one,
  1465. remote_two: remote_two
  1466. } do
  1467. {:ok, _, _} = Pleroma.User.follow(remote_one, local_user)
  1468. {:ok, _, _} = Pleroma.User.follow(remote_two, local_user)
  1469. {:ok, %{id: activity_id} = _activity} =
  1470. CommonAPI.post(local_user, %{status: "Happy Friday everyone!"})
  1471. # Generate the publish_one jobs
  1472. ObanHelpers.perform_all()
  1473. publish_one_jobs =
  1474. all_enqueued()
  1475. |> Enum.filter(fn job ->
  1476. match?(
  1477. %{
  1478. state: "available",
  1479. queue: "federator_outgoing",
  1480. worker: "Pleroma.Workers.PublisherWorker",
  1481. args: %{"op" => "publish_one", "params" => %{"activity_id" => ^activity_id}}
  1482. },
  1483. job
  1484. )
  1485. end)
  1486. assert length(publish_one_jobs) == 2
  1487. # The delete should have triggered cancelling the publish_one jobs
  1488. assert {:ok, _delete} = CommonAPI.delete(activity_id, local_user)
  1489. # all_enqueued/1 will not return cancelled jobs
  1490. cancelled_jobs =
  1491. Oban.Job
  1492. |> where([j], j.worker == "Pleroma.Workers.PublisherWorker")
  1493. |> where([j], j.state == "cancelled")
  1494. |> where([j], j.args["op"] == "publish_one")
  1495. |> where([j], j.args["params"]["activity_id"] == ^activity_id)
  1496. |> Pleroma.Repo.all()
  1497. assert length(cancelled_jobs) == 2
  1498. end
  1499. test "when unfavoriting posts", %{
  1500. local_user: local_user,
  1501. remote_one: remote_user
  1502. } do
  1503. {:ok, activity} =
  1504. CommonAPI.post(remote_user, %{status: "I like turtles!"})
  1505. {:ok, %{id: favorite_id} = _favorite} =
  1506. CommonAPI.favorite(activity.id, local_user)
  1507. # Generate the publish_one jobs
  1508. ObanHelpers.perform_all()
  1509. publish_one_jobs =
  1510. all_enqueued()
  1511. |> Enum.filter(fn job ->
  1512. match?(
  1513. %{
  1514. state: "available",
  1515. queue: "federator_outgoing",
  1516. worker: "Pleroma.Workers.PublisherWorker",
  1517. args: %{"op" => "publish_one", "params" => %{"activity_id" => ^favorite_id}}
  1518. },
  1519. job
  1520. )
  1521. end)
  1522. assert length(publish_one_jobs) == 1
  1523. # The unfavorite should have triggered cancelling the publish_one jobs
  1524. assert {:ok, _unfavorite} = CommonAPI.unfavorite(activity.id, local_user)
  1525. # all_enqueued/1 will not return cancelled jobs
  1526. cancelled_jobs =
  1527. Oban.Job
  1528. |> where([j], j.worker == "Pleroma.Workers.PublisherWorker")
  1529. |> where([j], j.state == "cancelled")
  1530. |> where([j], j.args["op"] == "publish_one")
  1531. |> where([j], j.args["params"]["activity_id"] == ^favorite_id)
  1532. |> Pleroma.Repo.all()
  1533. assert length(cancelled_jobs) == 1
  1534. end
  1535. test "when unboosting posts", %{
  1536. local_user: local_user,
  1537. remote_one: remote_one,
  1538. remote_two: remote_two
  1539. } do
  1540. {:ok, _, _} = Pleroma.User.follow(remote_one, local_user)
  1541. {:ok, _, _} = Pleroma.User.follow(remote_two, local_user)
  1542. {:ok, activity} =
  1543. CommonAPI.post(remote_one, %{status: "This is an unpleasant post"})
  1544. {:ok, %{id: repeat_id} = _repeat} =
  1545. CommonAPI.repeat(activity.id, local_user)
  1546. # Generate the publish_one jobs
  1547. ObanHelpers.perform_all()
  1548. publish_one_jobs =
  1549. all_enqueued()
  1550. |> Enum.filter(fn job ->
  1551. match?(
  1552. %{
  1553. state: "available",
  1554. queue: "federator_outgoing",
  1555. worker: "Pleroma.Workers.PublisherWorker",
  1556. args: %{"op" => "publish_one", "params" => %{"activity_id" => ^repeat_id}}
  1557. },
  1558. job
  1559. )
  1560. end)
  1561. assert length(publish_one_jobs) == 2
  1562. # The unrepeat should have triggered cancelling the publish_one jobs
  1563. assert {:ok, _unfavorite} = CommonAPI.unrepeat(activity.id, local_user)
  1564. # all_enqueued/1 will not return cancelled jobs
  1565. cancelled_jobs =
  1566. Oban.Job
  1567. |> where([j], j.worker == "Pleroma.Workers.PublisherWorker")
  1568. |> where([j], j.state == "cancelled")
  1569. |> where([j], j.args["op"] == "publish_one")
  1570. |> where([j], j.args["params"]["activity_id"] == ^repeat_id)
  1571. |> Pleroma.Repo.all()
  1572. assert length(cancelled_jobs) == 2
  1573. end
  1574. test "when unreacting to posts", %{
  1575. local_user: local_user,
  1576. remote_one: remote_one,
  1577. remote_two: remote_two
  1578. } do
  1579. {:ok, _, _} = Pleroma.User.follow(remote_one, local_user)
  1580. {:ok, _, _} = Pleroma.User.follow(remote_two, local_user)
  1581. {:ok, %{id: activity_id}} =
  1582. CommonAPI.post(remote_one, %{status: "Gang gang!!!!"})
  1583. {:ok, %{id: react_id} = _react} =
  1584. CommonAPI.react_with_emoji(activity_id, local_user, "👍")
  1585. # Generate the publish_one jobs
  1586. ObanHelpers.perform_all()
  1587. publish_one_jobs =
  1588. all_enqueued()
  1589. |> Enum.filter(fn job ->
  1590. match?(
  1591. %{
  1592. state: "available",
  1593. queue: "federator_outgoing",
  1594. worker: "Pleroma.Workers.PublisherWorker",
  1595. args: %{"op" => "publish_one", "params" => %{"activity_id" => ^react_id}}
  1596. },
  1597. job
  1598. )
  1599. end)
  1600. assert length(publish_one_jobs) == 2
  1601. # The unreact should have triggered cancelling the publish_one jobs
  1602. assert {:ok, _unreact} = CommonAPI.unreact_with_emoji(activity_id, local_user, "👍")
  1603. # all_enqueued/1 will not return cancelled jobs
  1604. cancelled_jobs =
  1605. Oban.Job
  1606. |> where([j], j.worker == "Pleroma.Workers.PublisherWorker")
  1607. |> where([j], j.state == "cancelled")
  1608. |> where([j], j.args["op"] == "publish_one")
  1609. |> where([j], j.args["params"]["activity_id"] == ^react_id)
  1610. |> Pleroma.Repo.all()
  1611. assert length(cancelled_jobs) == 2
  1612. end
  1613. end
  1614. end