logo

pleroma

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

common_api_test.exs (61189B)


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