Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1434 lines
44KB

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