logo

pleroma

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

common_api_test.exs (38366B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.CommonAPITest do
  5. use Pleroma.DataCase
  6. use Oban.Testing, repo: Pleroma.Repo
  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.User
  14. alias Pleroma.Web.ActivityPub.ActivityPub
  15. alias Pleroma.Web.ActivityPub.Transmogrifier
  16. alias Pleroma.Web.ActivityPub.Visibility
  17. alias Pleroma.Web.AdminAPI.AccountView
  18. alias Pleroma.Web.CommonAPI
  19. import Pleroma.Factory
  20. import Mock
  21. import Ecto.Query, only: [from: 2]
  22. require Pleroma.Constants
  23. setup do: clear_config([:instance, :safe_dm_mentions])
  24. setup do: clear_config([:instance, :limit])
  25. setup do: clear_config([:instance, :max_pinned_statuses])
  26. describe "posting polls" do
  27. test "it posts a poll" do
  28. user = insert(:user)
  29. {:ok, activity} =
  30. CommonAPI.post(user, %{
  31. status: "who is the best",
  32. poll: %{expires_in: 600, options: ["reimu", "marisa"]}
  33. })
  34. object = Object.normalize(activity)
  35. assert object.data["type"] == "Question"
  36. assert object.data["oneOf"] |> length() == 2
  37. end
  38. end
  39. describe "blocking" do
  40. setup do
  41. blocker = insert(:user)
  42. blocked = insert(:user)
  43. User.follow(blocker, blocked)
  44. User.follow(blocked, blocker)
  45. %{blocker: blocker, blocked: blocked}
  46. end
  47. test "it blocks and federates", %{blocker: blocker, blocked: blocked} do
  48. clear_config([:instance, :federating], true)
  49. with_mock Pleroma.Web.Federator,
  50. publish: fn _ -> nil end do
  51. assert {:ok, block} = CommonAPI.block(blocker, blocked)
  52. assert block.local
  53. assert User.blocks?(blocker, blocked)
  54. refute User.following?(blocker, blocked)
  55. refute User.following?(blocked, blocker)
  56. assert called(Pleroma.Web.Federator.publish(block))
  57. end
  58. end
  59. test "it blocks and does not federate if outgoing blocks are disabled", %{
  60. blocker: blocker,
  61. blocked: blocked
  62. } do
  63. clear_config([:instance, :federating], true)
  64. clear_config([:activitypub, :outgoing_blocks], false)
  65. with_mock Pleroma.Web.Federator,
  66. publish: fn _ -> nil end do
  67. assert {:ok, block} = CommonAPI.block(blocker, blocked)
  68. assert block.local
  69. assert User.blocks?(blocker, blocked)
  70. refute User.following?(blocker, blocked)
  71. refute User.following?(blocked, blocker)
  72. refute called(Pleroma.Web.Federator.publish(block))
  73. end
  74. end
  75. end
  76. describe "posting chat messages" do
  77. setup do: clear_config([:instance, :chat_limit])
  78. test "it posts a chat message without content but with an attachment" do
  79. author = insert(:user)
  80. recipient = insert(:user)
  81. file = %Plug.Upload{
  82. content_type: "image/jpg",
  83. path: Path.absname("test/fixtures/image.jpg"),
  84. filename: "an_image.jpg"
  85. }
  86. {:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
  87. with_mocks([
  88. {
  89. Pleroma.Web.Streamer,
  90. [],
  91. [
  92. stream: fn _, _ ->
  93. nil
  94. end
  95. ]
  96. },
  97. {
  98. Pleroma.Web.Push,
  99. [],
  100. [
  101. send: fn _ -> nil end
  102. ]
  103. }
  104. ]) do
  105. {:ok, activity} =
  106. CommonAPI.post_chat_message(
  107. author,
  108. recipient,
  109. nil,
  110. media_id: upload.id
  111. )
  112. notification =
  113. Notification.for_user_and_activity(recipient, activity)
  114. |> Repo.preload(:activity)
  115. assert called(Pleroma.Web.Push.send(notification))
  116. assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
  117. assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
  118. assert activity
  119. end
  120. end
  121. test "it adds html newlines" do
  122. author = insert(:user)
  123. recipient = insert(:user)
  124. other_user = insert(:user)
  125. {:ok, activity} =
  126. CommonAPI.post_chat_message(
  127. author,
  128. recipient,
  129. "uguu\nuguuu"
  130. )
  131. assert other_user.ap_id not in activity.recipients
  132. object = Object.normalize(activity, false)
  133. assert object.data["content"] == "uguu<br/>uguuu"
  134. end
  135. test "it linkifies" do
  136. author = insert(:user)
  137. recipient = insert(:user)
  138. other_user = insert(:user)
  139. {:ok, activity} =
  140. CommonAPI.post_chat_message(
  141. author,
  142. recipient,
  143. "https://example.org is the site of @#{other_user.nickname} #2hu"
  144. )
  145. assert other_user.ap_id not in activity.recipients
  146. object = Object.normalize(activity, false)
  147. assert object.data["content"] ==
  148. "<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=\"#{
  149. other_user.id
  150. }\" 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>"
  151. end
  152. test "it posts a chat message" do
  153. author = insert(:user)
  154. recipient = insert(:user)
  155. {:ok, activity} =
  156. CommonAPI.post_chat_message(
  157. author,
  158. recipient,
  159. "a test message <script>alert('uuu')</script> :firefox:"
  160. )
  161. assert activity.data["type"] == "Create"
  162. assert activity.local
  163. object = Object.normalize(activity)
  164. assert object.data["type"] == "ChatMessage"
  165. assert object.data["to"] == [recipient.ap_id]
  166. assert object.data["content"] ==
  167. "a test message &lt;script&gt;alert(&#39;uuu&#39;)&lt;/script&gt; :firefox:"
  168. assert object.data["emoji"] == %{
  169. "firefox" => "http://localhost:4001/emoji/Firefox.gif"
  170. }
  171. assert Chat.get(author.id, recipient.ap_id)
  172. assert Chat.get(recipient.id, author.ap_id)
  173. assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
  174. end
  175. test "it reject messages over the local limit" do
  176. Pleroma.Config.put([:instance, :chat_limit], 2)
  177. author = insert(:user)
  178. recipient = insert(:user)
  179. {:error, message} =
  180. CommonAPI.post_chat_message(
  181. author,
  182. recipient,
  183. "123"
  184. )
  185. assert message == :content_too_long
  186. end
  187. test "it reject messages via MRF" do
  188. clear_config([:mrf_keyword, :reject], ["GNO"])
  189. clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
  190. author = insert(:user)
  191. recipient = insert(:user)
  192. assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} ==
  193. CommonAPI.post_chat_message(author, recipient, "GNO/Linux")
  194. end
  195. end
  196. describe "unblocking" do
  197. test "it works even without an existing block activity" do
  198. blocked = insert(:user)
  199. blocker = insert(:user)
  200. User.block(blocker, blocked)
  201. assert User.blocks?(blocker, blocked)
  202. assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
  203. refute User.blocks?(blocker, blocked)
  204. end
  205. end
  206. describe "deletion" do
  207. test "it works with pruned objects" do
  208. user = insert(:user)
  209. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  210. clear_config([:instance, :federating], true)
  211. Object.normalize(post, false)
  212. |> Object.prune()
  213. with_mock Pleroma.Web.Federator,
  214. publish: fn _ -> nil end do
  215. assert {:ok, delete} = CommonAPI.delete(post.id, user)
  216. assert delete.local
  217. assert called(Pleroma.Web.Federator.publish(delete))
  218. end
  219. refute Activity.get_by_id(post.id)
  220. end
  221. test "it allows users to delete their posts" do
  222. user = insert(:user)
  223. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  224. clear_config([:instance, :federating], true)
  225. with_mock Pleroma.Web.Federator,
  226. publish: fn _ -> nil end do
  227. assert {:ok, delete} = CommonAPI.delete(post.id, user)
  228. assert delete.local
  229. assert called(Pleroma.Web.Federator.publish(delete))
  230. end
  231. refute Activity.get_by_id(post.id)
  232. end
  233. test "it does not allow a user to delete their posts" do
  234. user = insert(:user)
  235. other_user = insert(:user)
  236. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  237. assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
  238. assert Activity.get_by_id(post.id)
  239. end
  240. test "it allows moderators to delete other user's posts" do
  241. user = insert(:user)
  242. moderator = insert(:user, is_moderator: true)
  243. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  244. assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
  245. assert delete.local
  246. refute Activity.get_by_id(post.id)
  247. end
  248. test "it allows admins to delete other user's posts" do
  249. user = insert(:user)
  250. moderator = insert(:user, is_admin: true)
  251. {:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
  252. assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
  253. assert delete.local
  254. refute Activity.get_by_id(post.id)
  255. end
  256. test "superusers deleting non-local posts won't federate the delete" do
  257. # This is the user of the ingested activity
  258. _user =
  259. insert(:user,
  260. local: false,
  261. ap_id: "http://mastodon.example.org/users/admin",
  262. last_refreshed_at: NaiveDateTime.utc_now()
  263. )
  264. moderator = insert(:user, is_admin: true)
  265. data =
  266. File.read!("test/fixtures/mastodon-post-activity.json")
  267. |> Jason.decode!()
  268. {:ok, post} = Transmogrifier.handle_incoming(data)
  269. with_mock Pleroma.Web.Federator,
  270. publish: fn _ -> nil end do
  271. assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
  272. assert delete.local
  273. refute called(Pleroma.Web.Federator.publish(:_))
  274. end
  275. refute Activity.get_by_id(post.id)
  276. end
  277. end
  278. test "favoriting race condition" do
  279. user = insert(:user)
  280. users_serial = insert_list(10, :user)
  281. users = insert_list(10, :user)
  282. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  283. users_serial
  284. |> Enum.map(fn user ->
  285. CommonAPI.favorite(user, activity.id)
  286. end)
  287. object = Object.get_by_ap_id(activity.data["object"])
  288. assert object.data["like_count"] == 10
  289. users
  290. |> Enum.map(fn user ->
  291. Task.async(fn ->
  292. CommonAPI.favorite(user, activity.id)
  293. end)
  294. end)
  295. |> Enum.map(&Task.await/1)
  296. object = Object.get_by_ap_id(activity.data["object"])
  297. assert object.data["like_count"] == 20
  298. end
  299. test "repeating race condition" do
  300. user = insert(:user)
  301. users_serial = insert_list(10, :user)
  302. users = insert_list(10, :user)
  303. {:ok, activity} = CommonAPI.post(user, %{status: "."})
  304. users_serial
  305. |> Enum.map(fn user ->
  306. CommonAPI.repeat(activity.id, user)
  307. end)
  308. object = Object.get_by_ap_id(activity.data["object"])
  309. assert object.data["announcement_count"] == 10
  310. users
  311. |> Enum.map(fn user ->
  312. Task.async(fn ->
  313. CommonAPI.repeat(activity.id, user)
  314. end)
  315. end)
  316. |> Enum.map(&Task.await/1)
  317. object = Object.get_by_ap_id(activity.data["object"])
  318. assert object.data["announcement_count"] == 20
  319. end
  320. test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
  321. user = insert(:user)
  322. {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
  323. [participation] = Participation.for_user(user)
  324. {:ok, convo_reply} =
  325. CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
  326. assert Visibility.is_direct?(convo_reply)
  327. assert activity.data["context"] == convo_reply.data["context"]
  328. end
  329. test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
  330. har = insert(:user)
  331. jafnhar = insert(:user)
  332. tridi = insert(:user)
  333. {:ok, activity} =
  334. CommonAPI.post(har, %{
  335. status: "@#{jafnhar.nickname} hey",
  336. visibility: "direct"
  337. })
  338. assert har.ap_id in activity.recipients
  339. assert jafnhar.ap_id in activity.recipients
  340. [participation] = Participation.for_user(har)
  341. {:ok, activity} =
  342. CommonAPI.post(har, %{
  343. status: "I don't really like @#{tridi.nickname}",
  344. visibility: "direct",
  345. in_reply_to_status_id: activity.id,
  346. in_reply_to_conversation_id: participation.id
  347. })
  348. assert har.ap_id in activity.recipients
  349. assert jafnhar.ap_id in activity.recipients
  350. refute tridi.ap_id in activity.recipients
  351. end
  352. test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
  353. har = insert(:user)
  354. jafnhar = insert(:user)
  355. tridi = insert(:user)
  356. Pleroma.Config.put([:instance, :safe_dm_mentions], true)
  357. {:ok, activity} =
  358. CommonAPI.post(har, %{
  359. status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
  360. visibility: "direct"
  361. })
  362. refute tridi.ap_id in activity.recipients
  363. assert jafnhar.ap_id in activity.recipients
  364. end
  365. test "it de-duplicates tags" do
  366. user = insert(:user)
  367. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
  368. object = Object.normalize(activity)
  369. assert object.data["hashtags"] == ["2hu"]
  370. end
  371. test "it adds emoji in the object" do
  372. user = insert(:user)
  373. {:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
  374. assert Object.normalize(activity).data["emoji"]["firefox"]
  375. end
  376. describe "posting" do
  377. test "deactivated users can't post" do
  378. user = insert(:user, deactivated: true)
  379. assert {:error, _} = CommonAPI.post(user, %{status: "ye"})
  380. end
  381. test "it supports explicit addressing" do
  382. user = insert(:user)
  383. user_two = insert(:user)
  384. user_three = insert(:user)
  385. user_four = insert(:user)
  386. {:ok, activity} =
  387. CommonAPI.post(user, %{
  388. status:
  389. "Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
  390. to: [user_two.nickname, user_four.nickname, "nonexistent"]
  391. })
  392. assert user.ap_id in activity.recipients
  393. assert user_two.ap_id in activity.recipients
  394. assert user_four.ap_id in activity.recipients
  395. refute user_three.ap_id in activity.recipients
  396. end
  397. test "it filters out obviously bad tags when accepting a post as HTML" do
  398. user = insert(:user)
  399. post = "<p><b>2hu</b></p><script>alert('xss')</script>"
  400. {:ok, activity} =
  401. CommonAPI.post(user, %{
  402. status: post,
  403. content_type: "text/html"
  404. })
  405. object = Object.normalize(activity)
  406. assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
  407. assert object.data["source"] == post
  408. end
  409. test "it filters out obviously bad tags when accepting a post as Markdown" do
  410. user = insert(:user)
  411. post = "<p><b>2hu</b></p><script>alert('xss')</script>"
  412. {:ok, activity} =
  413. CommonAPI.post(user, %{
  414. status: post,
  415. content_type: "text/markdown"
  416. })
  417. object = Object.normalize(activity)
  418. assert object.data["content"] == "<p><b>2hu</b></p>alert(&#39;xss&#39;)"
  419. assert object.data["source"] == post
  420. end
  421. test "it does not allow replies to direct messages that are not direct messages themselves" do
  422. user = insert(:user)
  423. {:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
  424. assert {:ok, _} =
  425. CommonAPI.post(user, %{
  426. status: "suya..",
  427. visibility: "direct",
  428. in_reply_to_status_id: activity.id
  429. })
  430. Enum.each(["public", "private", "unlisted"], fn visibility ->
  431. assert {:error, "The message visibility must be direct"} =
  432. CommonAPI.post(user, %{
  433. status: "suya..",
  434. visibility: visibility,
  435. in_reply_to_status_id: activity.id
  436. })
  437. end)
  438. end
  439. test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
  440. user = insert(:user)
  441. other_user = insert(:user)
  442. third_user = insert(:user)
  443. {:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
  444. {:ok, open_answer} =
  445. CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
  446. # The OP is implicitly added
  447. assert user.ap_id in open_answer.recipients
  448. {:ok, secret_answer} =
  449. CommonAPI.post(other_user, %{
  450. status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
  451. in_reply_to_status_id: post.id,
  452. visibility: "direct"
  453. })
  454. assert third_user.ap_id in secret_answer.recipients
  455. # The OP is not added
  456. refute user.ap_id in secret_answer.recipients
  457. end
  458. test "it allows to address a list" do
  459. user = insert(:user)
  460. {:ok, list} = Pleroma.List.create("foo", user)
  461. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
  462. assert activity.data["bcc"] == [list.ap_id]
  463. assert activity.recipients == [list.ap_id, user.ap_id]
  464. assert activity.data["listMessage"] == list.ap_id
  465. end
  466. test "it returns error when status is empty and no attachments" do
  467. user = insert(:user)
  468. assert {:error, "Cannot post an empty status without attachments"} =
  469. CommonAPI.post(user, %{status: ""})
  470. end
  471. test "it validates character limits are correctly enforced" do
  472. Pleroma.Config.put([:instance, :limit], 5)
  473. user = insert(:user)
  474. assert {:error, "The status is over the character limit"} =
  475. CommonAPI.post(user, %{status: "foobar"})
  476. assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"})
  477. end
  478. test "it can handle activities that expire" do
  479. user = insert(:user)
  480. expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
  481. assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
  482. assert_enqueued(
  483. worker: Pleroma.Workers.PurgeExpiredActivity,
  484. args: %{activity_id: activity.id},
  485. scheduled_at: expires_at
  486. )
  487. end
  488. end
  489. describe "reactions" do
  490. test "reacting to a status with an emoji" do
  491. user = insert(:user)
  492. other_user = insert(:user)
  493. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  494. {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  495. assert reaction.data["actor"] == user.ap_id
  496. assert reaction.data["content"] == "👍"
  497. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  498. {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
  499. end
  500. test "unreacting to a status with an emoji" do
  501. user = insert(:user)
  502. other_user = insert(:user)
  503. clear_config([:instance, :federating], true)
  504. with_mock Pleroma.Web.Federator,
  505. publish: fn _ -> nil end do
  506. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  507. {:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
  508. {:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
  509. assert unreaction.data["type"] == "Undo"
  510. assert unreaction.data["object"] == reaction.data["id"]
  511. assert unreaction.local
  512. # On federation, it contains the undone (and deleted) object
  513. unreaction_with_object = %{
  514. unreaction
  515. | data: Map.put(unreaction.data, "object", reaction.data)
  516. }
  517. assert called(Pleroma.Web.Federator.publish(unreaction_with_object))
  518. end
  519. end
  520. test "repeating a status" do
  521. user = insert(:user)
  522. other_user = insert(:user)
  523. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  524. {:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
  525. assert Visibility.is_public?(announce_activity)
  526. end
  527. test "can't repeat a repeat" do
  528. user = insert(:user)
  529. other_user = insert(:user)
  530. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  531. {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
  532. refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
  533. end
  534. test "repeating a status privately" do
  535. user = insert(:user)
  536. other_user = insert(:user)
  537. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  538. {:ok, %Activity{} = announce_activity} =
  539. CommonAPI.repeat(activity.id, user, %{visibility: "private"})
  540. assert Visibility.is_private?(announce_activity)
  541. refute Visibility.visible_for_user?(announce_activity, nil)
  542. end
  543. test "favoriting a status" do
  544. user = insert(:user)
  545. other_user = insert(:user)
  546. {:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
  547. {:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
  548. assert data["type"] == "Like"
  549. assert data["actor"] == user.ap_id
  550. assert data["object"] == post_activity.data["object"]
  551. end
  552. test "retweeting a status twice returns the status" do
  553. user = insert(:user)
  554. other_user = insert(:user)
  555. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  556. {:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
  557. {:ok, ^announce} = CommonAPI.repeat(activity.id, user)
  558. end
  559. test "favoriting a status twice returns ok, but without the like activity" do
  560. user = insert(:user)
  561. other_user = insert(:user)
  562. {:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
  563. {:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
  564. assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
  565. end
  566. end
  567. describe "pinned statuses" do
  568. setup do
  569. Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
  570. user = insert(:user)
  571. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
  572. [user: user, activity: activity]
  573. end
  574. test "pin status", %{user: user, activity: activity} do
  575. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  576. id = activity.id
  577. user = refresh_record(user)
  578. assert %User{pinned_activities: [^id]} = user
  579. end
  580. test "pin poll", %{user: user} do
  581. {:ok, activity} =
  582. CommonAPI.post(user, %{
  583. status: "How is fediverse today?",
  584. poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
  585. })
  586. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  587. id = activity.id
  588. user = refresh_record(user)
  589. assert %User{pinned_activities: [^id]} = user
  590. end
  591. test "unlisted statuses can be pinned", %{user: user} do
  592. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
  593. assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
  594. end
  595. test "only self-authored can be pinned", %{activity: activity} do
  596. user = insert(:user)
  597. assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
  598. end
  599. test "max pinned statuses", %{user: user, activity: activity_one} do
  600. {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
  601. assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
  602. user = refresh_record(user)
  603. assert {:error, "You have already pinned the maximum number of statuses"} =
  604. CommonAPI.pin(activity_two.id, user)
  605. end
  606. test "unpin status", %{user: user, activity: activity} do
  607. {:ok, activity} = CommonAPI.pin(activity.id, user)
  608. user = refresh_record(user)
  609. id = activity.id
  610. assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
  611. user = refresh_record(user)
  612. assert %User{pinned_activities: []} = user
  613. end
  614. test "should unpin when deleting a status", %{user: user, activity: activity} do
  615. {:ok, activity} = CommonAPI.pin(activity.id, user)
  616. user = refresh_record(user)
  617. assert {:ok, _} = CommonAPI.delete(activity.id, user)
  618. user = refresh_record(user)
  619. assert %User{pinned_activities: []} = user
  620. end
  621. end
  622. describe "mute tests" do
  623. setup do
  624. user = insert(:user)
  625. activity = insert(:note_activity)
  626. [user: user, activity: activity]
  627. end
  628. test "marks notifications as read after mute" do
  629. author = insert(:user)
  630. activity = insert(:note_activity, user: author)
  631. friend1 = insert(:user)
  632. friend2 = insert(:user)
  633. {:ok, reply_activity} =
  634. CommonAPI.post(
  635. friend2,
  636. %{
  637. status: "@#{author.nickname} @#{friend1.nickname} test reply",
  638. in_reply_to_status_id: activity.id
  639. }
  640. )
  641. {:ok, favorite_activity} = CommonAPI.favorite(friend2, activity.id)
  642. {:ok, repeat_activity} = CommonAPI.repeat(activity.id, friend1)
  643. assert Repo.aggregate(
  644. from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
  645. :count
  646. ) == 1
  647. unread_notifications =
  648. Repo.all(from(n in Notification, where: n.seen == false, where: n.user_id == ^author.id))
  649. assert Enum.any?(unread_notifications, fn n ->
  650. n.type == "favourite" && n.activity_id == favorite_activity.id
  651. end)
  652. assert Enum.any?(unread_notifications, fn n ->
  653. n.type == "reblog" && n.activity_id == repeat_activity.id
  654. end)
  655. assert Enum.any?(unread_notifications, fn n ->
  656. n.type == "mention" && n.activity_id == reply_activity.id
  657. end)
  658. {:ok, _} = CommonAPI.add_mute(author, activity)
  659. assert CommonAPI.thread_muted?(author, activity)
  660. assert Repo.aggregate(
  661. from(n in Notification, where: n.seen == false and n.user_id == ^friend1.id),
  662. :count
  663. ) == 1
  664. read_notifications =
  665. Repo.all(from(n in Notification, where: n.seen == true, where: n.user_id == ^author.id))
  666. assert Enum.any?(read_notifications, fn n ->
  667. n.type == "favourite" && n.activity_id == favorite_activity.id
  668. end)
  669. assert Enum.any?(read_notifications, fn n ->
  670. n.type == "reblog" && n.activity_id == repeat_activity.id
  671. end)
  672. assert Enum.any?(read_notifications, fn n ->
  673. n.type == "mention" && n.activity_id == reply_activity.id
  674. end)
  675. end
  676. test "add mute", %{user: user, activity: activity} do
  677. {:ok, _} = CommonAPI.add_mute(user, activity)
  678. assert CommonAPI.thread_muted?(user, activity)
  679. end
  680. test "remove mute", %{user: user, activity: activity} do
  681. CommonAPI.add_mute(user, activity)
  682. {:ok, _} = CommonAPI.remove_mute(user, activity)
  683. refute CommonAPI.thread_muted?(user, activity)
  684. end
  685. test "check that mutes can't be duplicate", %{user: user, activity: activity} do
  686. CommonAPI.add_mute(user, activity)
  687. {:error, _} = CommonAPI.add_mute(user, activity)
  688. end
  689. end
  690. describe "reports" do
  691. test "creates a report" do
  692. reporter = insert(:user)
  693. target_user = insert(:user)
  694. {:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
  695. reporter_ap_id = reporter.ap_id
  696. target_ap_id = target_user.ap_id
  697. activity_ap_id = activity.data["id"]
  698. comment = "foobar"
  699. report_data = %{
  700. account_id: target_user.id,
  701. comment: comment,
  702. status_ids: [activity.id]
  703. }
  704. note_obj = %{
  705. "type" => "Note",
  706. "id" => activity_ap_id,
  707. "content" => "foobar",
  708. "published" => activity.object.data["published"],
  709. "actor" => AccountView.render("show.json", %{user: target_user})
  710. }
  711. assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
  712. assert %Activity{
  713. actor: ^reporter_ap_id,
  714. data: %{
  715. "type" => "Flag",
  716. "content" => ^comment,
  717. "object" => [^target_ap_id, ^note_obj],
  718. "state" => "open"
  719. }
  720. } = flag_activity
  721. end
  722. test "updates report state" do
  723. [reporter, target_user] = insert_pair(:user)
  724. activity = insert(:note_activity, user: target_user)
  725. {:ok, %Activity{id: report_id}} =
  726. CommonAPI.report(reporter, %{
  727. account_id: target_user.id,
  728. comment: "I feel offended",
  729. status_ids: [activity.id]
  730. })
  731. {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
  732. assert report.data["state"] == "resolved"
  733. [reported_user, activity_id] = report.data["object"]
  734. assert reported_user == target_user.ap_id
  735. assert activity_id == activity.data["id"]
  736. end
  737. test "does not update report state when state is unsupported" do
  738. [reporter, target_user] = insert_pair(:user)
  739. activity = insert(:note_activity, user: target_user)
  740. {:ok, %Activity{id: report_id}} =
  741. CommonAPI.report(reporter, %{
  742. account_id: target_user.id,
  743. comment: "I feel offended",
  744. status_ids: [activity.id]
  745. })
  746. assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
  747. end
  748. test "updates state of multiple reports" do
  749. [reporter, target_user] = insert_pair(:user)
  750. activity = insert(:note_activity, user: target_user)
  751. {:ok, %Activity{id: first_report_id}} =
  752. CommonAPI.report(reporter, %{
  753. account_id: target_user.id,
  754. comment: "I feel offended",
  755. status_ids: [activity.id]
  756. })
  757. {:ok, %Activity{id: second_report_id}} =
  758. CommonAPI.report(reporter, %{
  759. account_id: target_user.id,
  760. comment: "I feel very offended!",
  761. status_ids: [activity.id]
  762. })
  763. {:ok, report_ids} =
  764. CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
  765. first_report = Activity.get_by_id(first_report_id)
  766. second_report = Activity.get_by_id(second_report_id)
  767. assert report_ids -- [first_report_id, second_report_id] == []
  768. assert first_report.data["state"] == "resolved"
  769. assert second_report.data["state"] == "resolved"
  770. end
  771. end
  772. describe "reblog muting" do
  773. setup do
  774. muter = insert(:user)
  775. muted = insert(:user)
  776. [muter: muter, muted: muted]
  777. end
  778. test "add a reblog mute", %{muter: muter, muted: muted} do
  779. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
  780. assert User.showing_reblogs?(muter, muted) == false
  781. end
  782. test "remove a reblog mute", %{muter: muter, muted: muted} do
  783. {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
  784. {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
  785. assert User.showing_reblogs?(muter, muted) == true
  786. end
  787. end
  788. describe "follow/2" do
  789. test "directly follows a non-locked local user" do
  790. [follower, followed] = insert_pair(:user)
  791. {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
  792. assert User.following?(follower, followed)
  793. end
  794. end
  795. describe "unfollow/2" do
  796. test "also unsubscribes a user" do
  797. [follower, followed] = insert_pair(:user)
  798. {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
  799. {:ok, _subscription} = User.subscribe(follower, followed)
  800. assert User.subscribed_to?(follower, followed)
  801. {:ok, follower} = CommonAPI.unfollow(follower, followed)
  802. refute User.subscribed_to?(follower, followed)
  803. end
  804. test "cancels a pending follow for a local user" do
  805. follower = insert(:user)
  806. followed = insert(:user, locked: true)
  807. assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
  808. CommonAPI.follow(follower, followed)
  809. assert User.get_follow_state(follower, followed) == :follow_pending
  810. assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
  811. assert User.get_follow_state(follower, followed) == nil
  812. assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
  813. Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
  814. assert %{
  815. data: %{
  816. "type" => "Undo",
  817. "object" => %{"type" => "Follow", "state" => "cancelled"}
  818. }
  819. } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
  820. end
  821. test "cancels a pending follow for a remote user" do
  822. follower = insert(:user)
  823. followed = insert(:user, locked: true, local: false, ap_enabled: true)
  824. assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
  825. CommonAPI.follow(follower, followed)
  826. assert User.get_follow_state(follower, followed) == :follow_pending
  827. assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
  828. assert User.get_follow_state(follower, followed) == nil
  829. assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
  830. Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
  831. assert %{
  832. data: %{
  833. "type" => "Undo",
  834. "object" => %{"type" => "Follow", "state" => "cancelled"}
  835. }
  836. } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
  837. end
  838. end
  839. describe "accept_follow_request/2" do
  840. test "after acceptance, it sets all existing pending follow request states to 'accept'" do
  841. user = insert(:user, locked: true)
  842. follower = insert(:user)
  843. follower_two = insert(:user)
  844. {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
  845. {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
  846. {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
  847. assert follow_activity.data["state"] == "pending"
  848. assert follow_activity_two.data["state"] == "pending"
  849. assert follow_activity_three.data["state"] == "pending"
  850. {:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
  851. assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
  852. assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
  853. assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
  854. end
  855. test "after rejection, it sets all existing pending follow request states to 'reject'" do
  856. user = insert(:user, locked: true)
  857. follower = insert(:user)
  858. follower_two = insert(:user)
  859. {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
  860. {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
  861. {:ok, _, _, follow_activity_three} = CommonAPI.follow(follower_two, user)
  862. assert follow_activity.data["state"] == "pending"
  863. assert follow_activity_two.data["state"] == "pending"
  864. assert follow_activity_three.data["state"] == "pending"
  865. {:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
  866. assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
  867. assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
  868. assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
  869. end
  870. test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
  871. user = insert(:user, locked: true)
  872. not_follower = insert(:user)
  873. CommonAPI.accept_follow_request(not_follower, user)
  874. assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
  875. end
  876. end
  877. describe "vote/3" do
  878. test "does not allow to vote twice" do
  879. user = insert(:user)
  880. other_user = insert(:user)
  881. {:ok, activity} =
  882. CommonAPI.post(user, %{
  883. status: "Am I cute?",
  884. poll: %{options: ["Yes", "No"], expires_in: 20}
  885. })
  886. object = Object.normalize(activity)
  887. {:ok, _, object} = CommonAPI.vote(other_user, object, [0])
  888. assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
  889. end
  890. end
  891. describe "listen/2" do
  892. test "returns a valid activity" do
  893. user = insert(:user)
  894. {:ok, activity} =
  895. CommonAPI.listen(user, %{
  896. title: "lain radio episode 1",
  897. album: "lain radio",
  898. artist: "lain",
  899. length: 180_000
  900. })
  901. object = Object.normalize(activity)
  902. assert object.data["title"] == "lain radio episode 1"
  903. assert Visibility.get_visibility(activity) == "public"
  904. end
  905. test "respects visibility=private" do
  906. user = insert(:user)
  907. {:ok, activity} =
  908. CommonAPI.listen(user, %{
  909. title: "lain radio episode 1",
  910. album: "lain radio",
  911. artist: "lain",
  912. length: 180_000,
  913. visibility: "private"
  914. })
  915. object = Object.normalize(activity)
  916. assert object.data["title"] == "lain radio episode 1"
  917. assert Visibility.get_visibility(activity) == "private"
  918. end
  919. end
  920. describe "get_user/1" do
  921. test "gets user by ap_id" do
  922. user = insert(:user)
  923. assert CommonAPI.get_user(user.ap_id) == user
  924. end
  925. test "gets user by guessed nickname" do
  926. user = insert(:user, ap_id: "", nickname: "mario@mushroom.kingdom")
  927. assert CommonAPI.get_user("https://mushroom.kingdom/users/mario") == user
  928. end
  929. test "fallback" do
  930. assert %User{
  931. name: "",
  932. ap_id: "",
  933. nickname: "erroruser@example.com"
  934. } = CommonAPI.get_user("")
  935. end
  936. end
  937. end