logo

pleroma

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

status_controller_test.exs (81166B)


  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.MastodonAPI.StatusControllerTest do
  5. use Pleroma.Web.ConnCase, async: false
  6. use Oban.Testing, repo: Pleroma.Repo
  7. alias Pleroma.Activity
  8. alias Pleroma.Conversation.Participation
  9. alias Pleroma.ModerationLog
  10. alias Pleroma.Object
  11. alias Pleroma.Repo
  12. alias Pleroma.ScheduledActivity
  13. alias Pleroma.Tests.Helpers
  14. alias Pleroma.Tests.ObanHelpers
  15. alias Pleroma.User
  16. alias Pleroma.Web.ActivityPub.ActivityPub
  17. alias Pleroma.Web.ActivityPub.Utils
  18. alias Pleroma.Web.CommonAPI
  19. alias Pleroma.Workers.ScheduledActivityWorker
  20. import Mox
  21. import Pleroma.Factory
  22. setup do: clear_config([:instance, :federating])
  23. setup do: clear_config([:instance, :allow_relay])
  24. setup do: clear_config([:mrf, :policies])
  25. setup do: clear_config([:mrf_keyword, :reject])
  26. setup do
  27. Pleroma.UnstubbedConfigMock
  28. |> stub_with(Pleroma.Config)
  29. Pleroma.StaticStubbedConfigMock
  30. |> stub(:get, fn
  31. [:rich_media, :enabled] -> false
  32. path -> Pleroma.Test.StaticConfig.get(path)
  33. end)
  34. :ok
  35. end
  36. describe "posting statuses" do
  37. setup do: oauth_access(["write:statuses"])
  38. test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
  39. clear_config([:instance, :federating], true)
  40. clear_config([:instance, :allow_relay], true)
  41. response =
  42. conn
  43. |> put_req_header("content-type", "application/json")
  44. |> post("/api/v1/statuses", %{
  45. "content_type" => "text/plain",
  46. "source" => "Pleroma FE",
  47. "status" => "Hello world",
  48. "visibility" => "public"
  49. })
  50. |> json_response_and_validate_schema(200)
  51. assert response["reblogs_count"] == 0
  52. ObanHelpers.perform_all()
  53. response =
  54. conn
  55. |> get("/api/v1/statuses/#{response["id"]}", %{})
  56. |> json_response_and_validate_schema(200)
  57. assert response["reblogs_count"] == 0
  58. end
  59. test "posting a status", %{conn: conn} do
  60. idempotency_key = "Pikachu rocks!"
  61. conn_one =
  62. conn
  63. |> put_req_header("content-type", "application/json")
  64. |> put_req_header("idempotency-key", idempotency_key)
  65. |> post("/api/v1/statuses", %{
  66. "status" => "cofe",
  67. "spoiler_text" => "2hu",
  68. "sensitive" => "0"
  69. })
  70. assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
  71. json_response_and_validate_schema(conn_one, 200)
  72. assert Activity.get_by_id(id)
  73. conn_two =
  74. conn
  75. |> put_req_header("content-type", "application/json")
  76. |> put_req_header("idempotency-key", idempotency_key)
  77. |> post("/api/v1/statuses", %{
  78. "status" => "cofe",
  79. "spoiler_text" => "2hu",
  80. "sensitive" => 0
  81. })
  82. # Idempotency plug response means detection fail
  83. assert %{"id" => second_id} = json_response(conn_two, 200)
  84. assert id == second_id
  85. conn_three =
  86. conn
  87. |> put_req_header("content-type", "application/json")
  88. |> post("/api/v1/statuses", %{
  89. "status" => "cofe",
  90. "spoiler_text" => "2hu",
  91. "sensitive" => "False"
  92. })
  93. assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
  94. refute id == third_id
  95. # An activity that will expire:
  96. # 2 hours
  97. expires_in = 2 * 60 * 60
  98. expires_at = DateTime.add(DateTime.utc_now(), expires_in)
  99. conn_four =
  100. conn
  101. |> put_req_header("content-type", "application/json")
  102. |> post("/api/v1/statuses", %{
  103. "status" => "oolong",
  104. "expires_in" => expires_in
  105. })
  106. assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
  107. assert Activity.get_by_id(fourth_id)
  108. assert_enqueued(
  109. worker: Pleroma.Workers.PurgeExpiredActivity,
  110. args: %{activity_id: fourth_id},
  111. scheduled_at: expires_at
  112. )
  113. end
  114. test "posting a quote post", %{conn: conn} do
  115. user = insert(:user)
  116. {:ok, %{id: activity_id} = activity} = CommonAPI.post(user, %{status: "yolo"})
  117. %{data: %{"id" => quote_url}} = Object.normalize(activity)
  118. conn =
  119. conn
  120. |> put_req_header("content-type", "application/json")
  121. |> post("/api/v1/statuses", %{
  122. "status" => "indeed",
  123. "quote_id" => activity_id
  124. })
  125. assert %{
  126. "id" => id,
  127. "pleroma" => %{"quote" => %{"id" => ^activity_id}, "quote_url" => ^quote_url}
  128. } = json_response_and_validate_schema(conn, 200)
  129. assert Activity.get_by_id(id)
  130. end
  131. test "it fails to create a status if `expires_in` is less or equal than an hour", %{
  132. conn: conn
  133. } do
  134. # 1 minute
  135. expires_in = 1 * 60
  136. assert %{"error" => "Expiry date is too soon"} =
  137. conn
  138. |> put_req_header("content-type", "application/json")
  139. |> post("/api/v1/statuses", %{
  140. "status" => "oolong",
  141. "expires_in" => expires_in
  142. })
  143. |> json_response_and_validate_schema(422)
  144. # 5 minutes
  145. expires_in = 5 * 60
  146. assert %{"error" => "Expiry date is too soon"} =
  147. conn
  148. |> put_req_header("content-type", "application/json")
  149. |> post("/api/v1/statuses", %{
  150. "status" => "oolong",
  151. "expires_in" => expires_in
  152. })
  153. |> json_response_and_validate_schema(422)
  154. end
  155. test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
  156. clear_config([:mrf_keyword, :reject], ["GNO"])
  157. clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
  158. assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
  159. conn
  160. |> put_req_header("content-type", "application/json")
  161. |> post("/api/v1/statuses", %{"status" => "GNO/Linux"})
  162. |> json_response_and_validate_schema(422)
  163. end
  164. test "posting an undefined status with an attachment", %{user: user, conn: conn} do
  165. file = %Plug.Upload{
  166. content_type: "image/jpeg",
  167. path: Path.absname("test/fixtures/image.jpg"),
  168. filename: "an_image.jpg"
  169. }
  170. {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
  171. conn =
  172. conn
  173. |> put_req_header("content-type", "application/json")
  174. |> post("/api/v1/statuses", %{
  175. "media_ids" => [to_string(upload.id)]
  176. })
  177. assert json_response_and_validate_schema(conn, 200)
  178. end
  179. test "replying to a status", %{user: user, conn: conn} do
  180. {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
  181. conn =
  182. conn
  183. |> put_req_header("content-type", "application/json")
  184. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
  185. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
  186. activity = Activity.get_by_id(id)
  187. assert activity.data["context"] == replied_to.data["context"]
  188. assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
  189. end
  190. test "replying to a deleted status", %{user: user, conn: conn} do
  191. {:ok, status} = CommonAPI.post(user, %{status: "cofe"})
  192. {:ok, _deleted_status} = CommonAPI.delete(status.id, user)
  193. conn
  194. |> put_req_header("content-type", "application/json")
  195. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => status.id})
  196. |> json_response_and_validate_schema(422)
  197. end
  198. test "replying to a direct message with visibility other than direct", %{
  199. user: user,
  200. conn: conn
  201. } do
  202. {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
  203. Enum.each(["public", "private", "unlisted"], fn visibility ->
  204. conn =
  205. conn
  206. |> put_req_header("content-type", "application/json")
  207. |> post("/api/v1/statuses", %{
  208. "status" => "@#{user.nickname} hey",
  209. "in_reply_to_id" => replied_to.id,
  210. "visibility" => visibility
  211. })
  212. assert json_response_and_validate_schema(conn, 422) == %{
  213. "error" => "The message visibility must be direct"
  214. }
  215. end)
  216. end
  217. test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
  218. conn =
  219. conn
  220. |> put_req_header("content-type", "application/json")
  221. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
  222. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
  223. assert Activity.get_by_id(id)
  224. end
  225. test "posting a sensitive status", %{conn: conn} do
  226. conn =
  227. conn
  228. |> put_req_header("content-type", "application/json")
  229. |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
  230. assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
  231. json_response_and_validate_schema(conn, 200)
  232. assert Activity.get_by_id(id)
  233. end
  234. test "posting a fake status", %{conn: conn} do
  235. real_conn =
  236. conn
  237. |> put_req_header("content-type", "application/json")
  238. |> post("/api/v1/statuses", %{
  239. "status" =>
  240. "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
  241. })
  242. real_status = json_response_and_validate_schema(real_conn, 200)
  243. assert real_status
  244. assert Object.get_by_ap_id(real_status["uri"])
  245. real_status =
  246. real_status
  247. |> Map.put("id", nil)
  248. |> Map.put("url", nil)
  249. |> Map.put("uri", nil)
  250. |> Map.put("created_at", nil)
  251. |> Kernel.put_in(["pleroma", "context"], nil)
  252. |> Kernel.put_in(["pleroma", "conversation_id"], nil)
  253. fake_conn =
  254. conn
  255. |> assign(:user, refresh_record(conn.assigns.user))
  256. |> put_req_header("content-type", "application/json")
  257. |> post("/api/v1/statuses", %{
  258. "status" =>
  259. "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
  260. "preview" => true
  261. })
  262. fake_status = json_response_and_validate_schema(fake_conn, 200)
  263. assert fake_status
  264. refute Object.get_by_ap_id(fake_status["uri"])
  265. fake_status =
  266. fake_status
  267. |> Map.put("id", nil)
  268. |> Map.put("url", nil)
  269. |> Map.put("uri", nil)
  270. |> Map.put("created_at", nil)
  271. |> Kernel.put_in(["pleroma", "context"], nil)
  272. |> Kernel.put_in(["pleroma", "conversation_id"], nil)
  273. assert real_status == fake_status
  274. end
  275. test "posting a direct status", %{conn: conn} do
  276. user2 = insert(:user)
  277. content = "direct cofe @#{user2.nickname}"
  278. conn =
  279. conn
  280. |> put_req_header("content-type", "application/json")
  281. |> post("/api/v1/statuses", %{"status" => content, "visibility" => "direct"})
  282. assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
  283. assert response["visibility"] == "direct"
  284. assert response["pleroma"]["direct_conversation_id"]
  285. assert activity = Activity.get_by_id(id)
  286. assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
  287. assert activity.data["to"] == [user2.ap_id]
  288. assert activity.data["cc"] == []
  289. end
  290. test "discloses application metadata when enabled" do
  291. user = insert(:user, disclose_client: true)
  292. %{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
  293. %Pleroma.Web.OAuth.Token{
  294. app: %Pleroma.Web.OAuth.App{
  295. client_name: app_name,
  296. website: app_website
  297. }
  298. } = token
  299. result =
  300. conn
  301. |> put_req_header("content-type", "application/json")
  302. |> post("/api/v1/statuses", %{
  303. "status" => "cofe is my copilot"
  304. })
  305. assert %{
  306. "content" => "cofe is my copilot"
  307. } = json_response_and_validate_schema(result, 200)
  308. activity = result.assigns.activity.id
  309. result =
  310. conn
  311. |> get("/api/v1/statuses/#{activity}")
  312. assert %{
  313. "content" => "cofe is my copilot",
  314. "application" => %{
  315. "name" => ^app_name,
  316. "website" => ^app_website
  317. }
  318. } = json_response_and_validate_schema(result, 200)
  319. end
  320. test "hides application metadata when disabled" do
  321. user = insert(:user, disclose_client: false)
  322. %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
  323. result =
  324. conn
  325. |> put_req_header("content-type", "application/json")
  326. |> post("/api/v1/statuses", %{
  327. "status" => "club mate is my wingman"
  328. })
  329. assert %{"content" => "club mate is my wingman"} =
  330. json_response_and_validate_schema(result, 200)
  331. activity = result.assigns.activity.id
  332. result =
  333. conn
  334. |> get("/api/v1/statuses/#{activity}")
  335. assert %{
  336. "content" => "club mate is my wingman",
  337. "application" => nil
  338. } = json_response_and_validate_schema(result, 200)
  339. end
  340. end
  341. describe "posting scheduled statuses" do
  342. setup do: oauth_access(["write:statuses"])
  343. test "creates a scheduled activity", %{conn: conn} do
  344. scheduled_at =
  345. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
  346. |> NaiveDateTime.to_iso8601()
  347. |> Kernel.<>("Z")
  348. conn =
  349. conn
  350. |> put_req_header("content-type", "application/json")
  351. |> post("/api/v1/statuses", %{
  352. "status" => "scheduled",
  353. "scheduled_at" => scheduled_at
  354. })
  355. assert %{"scheduled_at" => expected_scheduled_at} =
  356. json_response_and_validate_schema(conn, 200)
  357. assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
  358. assert [] == Repo.all(Activity)
  359. end
  360. test "with expiration" do
  361. %{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
  362. scheduled_at =
  363. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
  364. |> NaiveDateTime.to_iso8601()
  365. |> Kernel.<>("Z")
  366. assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
  367. conn
  368. |> put_req_header("content-type", "application/json")
  369. |> post("/api/v1/statuses", %{
  370. "status" => "scheduled",
  371. "scheduled_at" => scheduled_at,
  372. "expires_in" => 300
  373. })
  374. |> json_response_and_validate_schema(200)
  375. assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
  376. conn
  377. |> put_req_header("content-type", "application/json")
  378. |> get("/api/v1/scheduled_statuses/#{status_id}")
  379. |> json_response_and_validate_schema(200)
  380. end
  381. test "ignores nil values", %{conn: conn} do
  382. conn =
  383. conn
  384. |> put_req_header("content-type", "application/json")
  385. |> post("/api/v1/statuses", %{
  386. "status" => "not scheduled",
  387. "scheduled_at" => nil
  388. })
  389. assert result = json_response_and_validate_schema(conn, 200)
  390. assert Activity.get_by_id(result["id"])
  391. end
  392. test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
  393. scheduled_at =
  394. NaiveDateTime.utc_now()
  395. |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
  396. |> NaiveDateTime.to_iso8601()
  397. |> Kernel.<>("Z")
  398. file = %Plug.Upload{
  399. content_type: "image/jpeg",
  400. path: Path.absname("test/fixtures/image.jpg"),
  401. filename: "an_image.jpg"
  402. }
  403. {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
  404. conn =
  405. conn
  406. |> put_req_header("content-type", "application/json")
  407. |> post("/api/v1/statuses", %{
  408. "media_ids" => [to_string(upload.id)],
  409. "status" => "scheduled",
  410. "scheduled_at" => scheduled_at
  411. })
  412. assert %{"media_attachments" => [media_attachment]} =
  413. json_response_and_validate_schema(conn, 200)
  414. assert %{"type" => "image"} = media_attachment
  415. end
  416. test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
  417. %{conn: conn} do
  418. scheduled_at =
  419. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
  420. |> NaiveDateTime.to_iso8601()
  421. |> Kernel.<>("Z")
  422. conn =
  423. conn
  424. |> put_req_header("content-type", "application/json")
  425. |> post("/api/v1/statuses", %{
  426. "status" => "not scheduled",
  427. "scheduled_at" => scheduled_at
  428. })
  429. assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
  430. assert [] == Repo.all(ScheduledActivity)
  431. end
  432. test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
  433. today =
  434. NaiveDateTime.utc_now()
  435. |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
  436. |> NaiveDateTime.to_iso8601()
  437. # TODO
  438. |> Kernel.<>("Z")
  439. attrs = %{params: %{}, scheduled_at: today}
  440. {:ok, _} = ScheduledActivity.create(user, attrs)
  441. {:ok, _} = ScheduledActivity.create(user, attrs)
  442. conn =
  443. conn
  444. |> put_req_header("content-type", "application/json")
  445. |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
  446. assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
  447. end
  448. test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
  449. today =
  450. NaiveDateTime.utc_now()
  451. |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
  452. |> NaiveDateTime.to_iso8601()
  453. |> Kernel.<>("Z")
  454. tomorrow =
  455. NaiveDateTime.utc_now()
  456. |> NaiveDateTime.add(:timer.hours(36), :millisecond)
  457. |> NaiveDateTime.to_iso8601()
  458. |> Kernel.<>("Z")
  459. attrs = %{params: %{}, scheduled_at: today}
  460. {:ok, _} = ScheduledActivity.create(user, attrs)
  461. {:ok, _} = ScheduledActivity.create(user, attrs)
  462. {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
  463. conn =
  464. conn
  465. |> put_req_header("content-type", "application/json")
  466. |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
  467. assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
  468. end
  469. end
  470. describe "posting polls" do
  471. setup do: oauth_access(["write:statuses"])
  472. test "posting a poll", %{conn: conn} do
  473. time = NaiveDateTime.utc_now()
  474. conn =
  475. conn
  476. |> put_req_header("content-type", "application/json")
  477. |> post("/api/v1/statuses", %{
  478. "status" => "Who is the #bestgrill?",
  479. "poll" => %{
  480. "options" => ["Rei", "Asuka", "Misato"],
  481. "expires_in" => 420
  482. }
  483. })
  484. response = json_response_and_validate_schema(conn, 200)
  485. assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
  486. title in ["Rei", "Asuka", "Misato"]
  487. end)
  488. assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
  489. assert response["poll"]["expired"] == false
  490. question = Object.get_by_id(response["poll"]["id"])
  491. # closed contains utc timezone
  492. assert question.data["closed"] =~ "Z"
  493. end
  494. test "option limit is enforced", %{conn: conn} do
  495. limit = Config.get([:instance, :poll_limits, :max_options])
  496. conn =
  497. conn
  498. |> put_req_header("content-type", "application/json")
  499. |> post("/api/v1/statuses", %{
  500. "status" => "desu~",
  501. "poll" => %{
  502. "options" => Enum.map(0..limit, fn num -> "desu #{num}" end),
  503. "expires_in" => 1
  504. }
  505. })
  506. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  507. assert error == "Poll can't contain more than #{limit} options"
  508. end
  509. test "option character limit is enforced", %{conn: conn} do
  510. limit = Config.get([:instance, :poll_limits, :max_option_chars])
  511. conn =
  512. conn
  513. |> put_req_header("content-type", "application/json")
  514. |> post("/api/v1/statuses", %{
  515. "status" => "...",
  516. "poll" => %{
  517. "options" => [String.duplicate(".", limit + 1), "lol"],
  518. "expires_in" => 1
  519. }
  520. })
  521. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  522. assert error == "Poll options cannot be longer than #{limit} characters each"
  523. end
  524. test "minimal date limit is enforced", %{conn: conn} do
  525. limit = Config.get([:instance, :poll_limits, :min_expiration])
  526. conn =
  527. conn
  528. |> put_req_header("content-type", "application/json")
  529. |> post("/api/v1/statuses", %{
  530. "status" => "imagine arbitrary limits",
  531. "poll" => %{
  532. "options" => ["this post was made by pleroma gang"],
  533. "expires_in" => limit - 1
  534. }
  535. })
  536. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  537. assert error == "Expiration date is too soon"
  538. end
  539. test "maximum date limit is enforced", %{conn: conn} do
  540. limit = Config.get([:instance, :poll_limits, :max_expiration])
  541. conn =
  542. conn
  543. |> put_req_header("content-type", "application/json")
  544. |> post("/api/v1/statuses", %{
  545. "status" => "imagine arbitrary limits",
  546. "poll" => %{
  547. "options" => ["this post was made by pleroma gang"],
  548. "expires_in" => limit + 1
  549. }
  550. })
  551. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  552. assert error == "Expiration date is too far in the future"
  553. end
  554. test "scheduled poll", %{conn: conn} do
  555. clear_config([ScheduledActivity, :enabled], true)
  556. scheduled_at =
  557. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
  558. |> NaiveDateTime.to_iso8601()
  559. |> Kernel.<>("Z")
  560. %{"id" => scheduled_id} =
  561. conn
  562. |> put_req_header("content-type", "application/json")
  563. |> post("/api/v1/statuses", %{
  564. "status" => "very cool poll",
  565. "poll" => %{
  566. "options" => ~w(a b c),
  567. "expires_in" => 420
  568. },
  569. "scheduled_at" => scheduled_at
  570. })
  571. |> json_response_and_validate_schema(200)
  572. assert {:ok, %{id: activity_id}} =
  573. perform_job(ScheduledActivityWorker, %{
  574. activity_id: scheduled_id
  575. })
  576. refute_enqueued(worker: ScheduledActivityWorker)
  577. object =
  578. Activity
  579. |> Repo.get(activity_id)
  580. |> Object.normalize()
  581. assert object.data["content"] == "very cool poll"
  582. assert object.data["type"] == "Question"
  583. assert length(object.data["oneOf"]) == 3
  584. end
  585. test "cannot have only one option", %{conn: conn} do
  586. conn =
  587. conn
  588. |> put_req_header("content-type", "application/json")
  589. |> post("/api/v1/statuses", %{
  590. "status" => "desu~",
  591. "poll" => %{"options" => ["mew"], "expires_in" => 1}
  592. })
  593. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  594. assert error == "Poll must contain at least 2 options"
  595. end
  596. test "cannot have only duplicated options", %{conn: conn} do
  597. conn =
  598. conn
  599. |> put_req_header("content-type", "application/json")
  600. |> post("/api/v1/statuses", %{
  601. "status" => "desu~",
  602. "poll" => %{"options" => ["mew", "mew"], "expires_in" => 1}
  603. })
  604. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  605. assert error == "Poll must contain at least 2 options"
  606. end
  607. end
  608. test "get a status" do
  609. %{conn: conn} = oauth_access(["read:statuses"])
  610. activity = insert(:note_activity)
  611. conn = get(conn, "/api/v1/statuses/#{activity.id}")
  612. assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
  613. assert id == to_string(activity.id)
  614. end
  615. defp local_and_remote_activities do
  616. local = insert(:note_activity)
  617. remote = insert(:note_activity, local: false)
  618. {:ok, local: local, remote: remote}
  619. end
  620. defp local_and_remote_context_activities do
  621. local_user_1 = insert(:user)
  622. local_user_2 = insert(:user)
  623. remote_user = insert(:user, local: false)
  624. {:ok, %{id: id1, data: %{"context" => context}}} =
  625. CommonAPI.post(local_user_1, %{status: "post"})
  626. {:ok, %{id: id2} = post} =
  627. CommonAPI.post(local_user_2, %{status: "local reply", in_reply_to_status_id: id1})
  628. params = %{
  629. "@context" => "https://www.w3.org/ns/activitystreams",
  630. "actor" => remote_user.ap_id,
  631. "type" => "Create",
  632. "context" => context,
  633. "id" => "#{remote_user.ap_id}/activities/1",
  634. "inReplyTo" => post.data["id"],
  635. "object" => %{
  636. "type" => "Note",
  637. "content" => "remote reply",
  638. "context" => context,
  639. "id" => "#{remote_user.ap_id}/objects/1",
  640. "attributedTo" => remote_user.ap_id,
  641. "to" => [
  642. local_user_1.ap_id,
  643. local_user_2.ap_id,
  644. "https://www.w3.org/ns/activitystreams#Public"
  645. ]
  646. },
  647. "to" => [
  648. local_user_1.ap_id,
  649. local_user_2.ap_id,
  650. "https://www.w3.org/ns/activitystreams#Public"
  651. ]
  652. }
  653. {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
  654. {:ok, remote_activity} = ObanHelpers.perform(job)
  655. %{locals: [id1, id2], remote: remote_activity.id, context: context}
  656. end
  657. describe "status with restrict unauthenticated activities for local and remote" do
  658. setup do: local_and_remote_activities()
  659. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  660. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  661. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  662. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  663. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  664. "error" => "Record not found"
  665. }
  666. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  667. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  668. "error" => "Record not found"
  669. }
  670. end
  671. test "if user is authenticated", %{local: local, remote: remote} do
  672. %{conn: conn} = oauth_access(["read"])
  673. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  674. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  675. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  676. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  677. end
  678. end
  679. describe "status with restrict unauthenticated activities for local" do
  680. setup do: local_and_remote_activities()
  681. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  682. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  683. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  684. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  685. "error" => "Record not found"
  686. }
  687. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  688. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  689. end
  690. test "if user is authenticated", %{local: local, remote: remote} do
  691. %{conn: conn} = oauth_access(["read"])
  692. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  693. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  694. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  695. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  696. end
  697. end
  698. describe "status with restrict unauthenticated activities for remote" do
  699. setup do: local_and_remote_activities()
  700. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  701. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  702. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  703. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  704. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  705. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  706. "error" => "Record not found"
  707. }
  708. end
  709. test "if user is authenticated", %{local: local, remote: remote} do
  710. %{conn: conn} = oauth_access(["read"])
  711. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  712. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  713. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  714. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  715. end
  716. end
  717. test "getting a status that doesn't exist returns 404" do
  718. %{conn: conn} = oauth_access(["read:statuses"])
  719. activity = insert(:note_activity)
  720. conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
  721. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  722. end
  723. test "get a direct status" do
  724. %{user: user, conn: conn} = oauth_access(["read:statuses"])
  725. other_user = insert(:user)
  726. {:ok, activity} =
  727. CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
  728. conn =
  729. conn
  730. |> assign(:user, user)
  731. |> get("/api/v1/statuses/#{activity.id}")
  732. [participation] = Participation.for_user(user)
  733. res = json_response_and_validate_schema(conn, 200)
  734. assert res["pleroma"]["direct_conversation_id"] == participation.id
  735. end
  736. test "get statuses by IDs" do
  737. %{conn: conn} = oauth_access(["read:statuses"])
  738. %{id: id1} = insert(:note_activity)
  739. %{id: id2} = insert(:note_activity)
  740. query_string = "id[]=#{id1}&id[]=#{id2}"
  741. conn = get(conn, "/api/v1/statuses/?#{query_string}")
  742. assert [%{"id" => ^id1}, %{"id" => ^id2}] =
  743. Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
  744. end
  745. test "get statuses by IDs falls back to ids[]" do
  746. %{conn: conn} = oauth_access(["read:statuses"])
  747. %{id: id} = insert(:note_activity)
  748. query_string = "ids[]=#{id}"
  749. conn = get(conn, "/api/v1/statuses/?#{query_string}")
  750. assert [%{"id" => ^id}] = json_response_and_validate_schema(conn, 200)
  751. end
  752. describe "getting statuses by ids with restricted unauthenticated for local and remote" do
  753. setup do: local_and_remote_activities()
  754. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  755. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  756. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  757. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  758. assert json_response_and_validate_schema(res_conn, 200) == []
  759. end
  760. test "if user is authenticated", %{local: local, remote: remote} do
  761. %{conn: conn} = oauth_access(["read"])
  762. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  763. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  764. end
  765. end
  766. describe "getting statuses by ids with restricted unauthenticated for local" do
  767. setup do: local_and_remote_activities()
  768. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  769. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  770. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  771. remote_id = remote.id
  772. assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
  773. end
  774. test "if user is authenticated", %{local: local, remote: remote} do
  775. %{conn: conn} = oauth_access(["read"])
  776. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  777. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  778. end
  779. end
  780. describe "getting statuses by ids with restricted unauthenticated for remote" do
  781. setup do: local_and_remote_activities()
  782. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  783. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  784. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  785. local_id = local.id
  786. assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
  787. end
  788. test "if user is authenticated", %{local: local, remote: remote} do
  789. %{conn: conn} = oauth_access(["read"])
  790. res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}")
  791. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  792. end
  793. end
  794. describe "getting status contexts restricted unauthenticated for local and remote" do
  795. setup do: local_and_remote_context_activities()
  796. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  797. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  798. test "if user is unauthenticated", %{conn: conn, locals: [post_id, _]} do
  799. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  800. assert json_response_and_validate_schema(res_conn, 200) == %{
  801. "ancestors" => [],
  802. "descendants" => []
  803. }
  804. end
  805. test "if user is unauthenticated reply", %{conn: conn, locals: [_, reply_id]} do
  806. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  807. assert json_response_and_validate_schema(res_conn, 200) == %{
  808. "ancestors" => [],
  809. "descendants" => []
  810. }
  811. end
  812. test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  813. %{conn: conn} = oauth_access(["read"])
  814. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  815. %{"ancestors" => [], "descendants" => descendants} =
  816. json_response_and_validate_schema(res_conn, 200)
  817. descendant_ids =
  818. descendants
  819. |> Enum.map(& &1["id"])
  820. assert reply_id in descendant_ids
  821. assert remote_reply_id in descendant_ids
  822. end
  823. test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  824. %{conn: conn} = oauth_access(["read"])
  825. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  826. %{"ancestors" => ancestors, "descendants" => descendants} =
  827. json_response_and_validate_schema(res_conn, 200)
  828. ancestor_ids =
  829. ancestors
  830. |> Enum.map(& &1["id"])
  831. descendant_ids =
  832. descendants
  833. |> Enum.map(& &1["id"])
  834. assert post_id in ancestor_ids
  835. assert remote_reply_id in descendant_ids
  836. end
  837. end
  838. describe "getting status contexts restricted unauthenticated for local" do
  839. setup do: local_and_remote_context_activities()
  840. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  841. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], false)
  842. test "if user is unauthenticated", %{
  843. conn: conn,
  844. locals: [post_id, reply_id],
  845. remote: remote_reply_id
  846. } do
  847. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  848. %{"ancestors" => [], "descendants" => descendants} =
  849. json_response_and_validate_schema(res_conn, 200)
  850. descendant_ids =
  851. descendants
  852. |> Enum.map(& &1["id"])
  853. assert reply_id not in descendant_ids
  854. assert remote_reply_id in descendant_ids
  855. end
  856. test "if user is unauthenticated reply", %{
  857. conn: conn,
  858. locals: [post_id, reply_id],
  859. remote: remote_reply_id
  860. } do
  861. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  862. %{"ancestors" => ancestors, "descendants" => descendants} =
  863. json_response_and_validate_schema(res_conn, 200)
  864. ancestor_ids =
  865. ancestors
  866. |> Enum.map(& &1["id"])
  867. descendant_ids =
  868. descendants
  869. |> Enum.map(& &1["id"])
  870. assert post_id not in ancestor_ids
  871. assert remote_reply_id in descendant_ids
  872. end
  873. test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  874. %{conn: conn} = oauth_access(["read"])
  875. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  876. %{"ancestors" => [], "descendants" => descendants} =
  877. json_response_and_validate_schema(res_conn, 200)
  878. descendant_ids =
  879. descendants
  880. |> Enum.map(& &1["id"])
  881. assert reply_id in descendant_ids
  882. assert remote_reply_id in descendant_ids
  883. end
  884. test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  885. %{conn: conn} = oauth_access(["read"])
  886. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  887. %{"ancestors" => ancestors, "descendants" => descendants} =
  888. json_response_and_validate_schema(res_conn, 200)
  889. ancestor_ids =
  890. ancestors
  891. |> Enum.map(& &1["id"])
  892. descendant_ids =
  893. descendants
  894. |> Enum.map(& &1["id"])
  895. assert post_id in ancestor_ids
  896. assert remote_reply_id in descendant_ids
  897. end
  898. end
  899. describe "getting status contexts restricted unauthenticated for remote" do
  900. setup do: local_and_remote_context_activities()
  901. setup do: clear_config([:restrict_unauthenticated, :activities, :local], false)
  902. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  903. test "if user is unauthenticated", %{
  904. conn: conn,
  905. locals: [post_id, reply_id],
  906. remote: remote_reply_id
  907. } do
  908. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  909. %{"ancestors" => [], "descendants" => descendants} =
  910. json_response_and_validate_schema(res_conn, 200)
  911. descendant_ids =
  912. descendants
  913. |> Enum.map(& &1["id"])
  914. assert reply_id in descendant_ids
  915. assert remote_reply_id not in descendant_ids
  916. end
  917. test "if user is unauthenticated reply", %{
  918. conn: conn,
  919. locals: [post_id, reply_id],
  920. remote: remote_reply_id
  921. } do
  922. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  923. %{"ancestors" => ancestors, "descendants" => descendants} =
  924. json_response_and_validate_schema(res_conn, 200)
  925. ancestor_ids =
  926. ancestors
  927. |> Enum.map(& &1["id"])
  928. descendant_ids =
  929. descendants
  930. |> Enum.map(& &1["id"])
  931. assert post_id in ancestor_ids
  932. assert remote_reply_id not in descendant_ids
  933. end
  934. test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  935. %{conn: conn} = oauth_access(["read"])
  936. res_conn = get(conn, "/api/v1/statuses/#{post_id}/context")
  937. %{"ancestors" => [], "descendants" => descendants} =
  938. json_response_and_validate_schema(res_conn, 200)
  939. reply_ids =
  940. descendants
  941. |> Enum.map(& &1["id"])
  942. assert reply_id in reply_ids
  943. assert remote_reply_id in reply_ids
  944. end
  945. test "if user is authenticated reply", %{locals: [post_id, reply_id], remote: remote_reply_id} do
  946. %{conn: conn} = oauth_access(["read"])
  947. res_conn = get(conn, "/api/v1/statuses/#{reply_id}/context")
  948. %{"ancestors" => ancestors, "descendants" => descendants} =
  949. json_response_and_validate_schema(res_conn, 200)
  950. ancestor_ids =
  951. ancestors
  952. |> Enum.map(& &1["id"])
  953. descendant_ids =
  954. descendants
  955. |> Enum.map(& &1["id"])
  956. assert post_id in ancestor_ids
  957. assert remote_reply_id in descendant_ids
  958. end
  959. end
  960. describe "deleting a status" do
  961. test "when you created it" do
  962. %{user: author, conn: conn} = oauth_access(["write:statuses"])
  963. activity = insert(:note_activity, user: author)
  964. object = Object.normalize(activity, fetch: false)
  965. content = object.data["content"]
  966. source = object.data["source"]
  967. result =
  968. conn
  969. |> assign(:user, author)
  970. |> delete("/api/v1/statuses/#{activity.id}")
  971. |> json_response_and_validate_schema(200)
  972. assert match?(%{"content" => ^content, "text" => ^source}, result)
  973. refute Activity.get_by_id(activity.id)
  974. end
  975. test "when it doesn't exist" do
  976. %{user: author, conn: conn} = oauth_access(["write:statuses"])
  977. activity = insert(:note_activity, user: author)
  978. conn =
  979. conn
  980. |> assign(:user, author)
  981. |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
  982. assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
  983. end
  984. test "when you didn't create it" do
  985. %{conn: conn} = oauth_access(["write:statuses"])
  986. activity = insert(:note_activity)
  987. conn = delete(conn, "/api/v1/statuses/#{activity.id}")
  988. assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
  989. assert Activity.get_by_id(activity.id) == activity
  990. end
  991. test "when you're privileged to", %{conn: conn} do
  992. clear_config([:instance, :moderator_privileges], [:messages_delete])
  993. activity = insert(:note_activity)
  994. user = insert(:user, is_moderator: true)
  995. res_conn =
  996. conn
  997. |> assign(:user, user)
  998. |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
  999. |> delete("/api/v1/statuses/#{activity.id}")
  1000. assert %{} = json_response_and_validate_schema(res_conn, 200)
  1001. assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
  1002. "@#{user.nickname} deleted status ##{activity.id}"
  1003. refute Activity.get_by_id(activity.id)
  1004. end
  1005. test "when you're privileged and the user is banned", %{conn: conn} do
  1006. clear_config([:instance, :moderator_privileges], [:messages_delete])
  1007. posting_user = insert(:user, is_active: false)
  1008. refute posting_user.is_active
  1009. activity = insert(:note_activity, user: posting_user)
  1010. user = insert(:user, is_moderator: true)
  1011. res_conn =
  1012. conn
  1013. |> assign(:user, user)
  1014. |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
  1015. |> delete("/api/v1/statuses/#{activity.id}")
  1016. assert %{} = json_response_and_validate_schema(res_conn, 200)
  1017. assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
  1018. "@#{user.nickname} deleted status ##{activity.id}"
  1019. refute Activity.get_by_id(activity.id)
  1020. end
  1021. end
  1022. describe "reblogging" do
  1023. setup do: oauth_access(["write:statuses"])
  1024. test "reblogs and returns the reblogged status", %{conn: conn} do
  1025. activity = insert(:note_activity)
  1026. conn =
  1027. conn
  1028. |> put_req_header("content-type", "application/json")
  1029. |> post("/api/v1/statuses/#{activity.id}/reblog")
  1030. assert %{
  1031. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
  1032. "reblogged" => true
  1033. } = json_response_and_validate_schema(conn, 200)
  1034. assert to_string(activity.id) == id
  1035. end
  1036. test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
  1037. activity = insert(:note_activity)
  1038. conn =
  1039. conn
  1040. |> put_req_header("content-type", "application/json")
  1041. |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
  1042. assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
  1043. end
  1044. test "reblogs privately and returns the reblogged status", %{conn: conn} do
  1045. activity = insert(:note_activity)
  1046. conn =
  1047. conn
  1048. |> put_req_header("content-type", "application/json")
  1049. |> post(
  1050. "/api/v1/statuses/#{activity.id}/reblog",
  1051. %{"visibility" => "private"}
  1052. )
  1053. assert %{
  1054. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
  1055. "reblogged" => true,
  1056. "visibility" => "private"
  1057. } = json_response_and_validate_schema(conn, 200)
  1058. assert to_string(activity.id) == id
  1059. end
  1060. test "reblogged status for another user" do
  1061. activity = insert(:note_activity)
  1062. user1 = insert(:user)
  1063. user2 = insert(:user)
  1064. user3 = insert(:user)
  1065. {:ok, _} = CommonAPI.favorite(activity.id, user2)
  1066. {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
  1067. {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
  1068. {:ok, _} = CommonAPI.repeat(activity.id, user2)
  1069. conn_res =
  1070. build_conn()
  1071. |> assign(:user, user3)
  1072. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
  1073. |> get("/api/v1/statuses/#{reblog_activity1.id}")
  1074. assert %{
  1075. "reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
  1076. "reblogged" => false,
  1077. "favourited" => false,
  1078. "bookmarked" => false
  1079. } = json_response_and_validate_schema(conn_res, 200)
  1080. conn_res =
  1081. build_conn()
  1082. |> assign(:user, user2)
  1083. |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
  1084. |> get("/api/v1/statuses/#{reblog_activity1.id}")
  1085. assert %{
  1086. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
  1087. "reblogged" => true,
  1088. "favourited" => true,
  1089. "bookmarked" => true
  1090. } = json_response_and_validate_schema(conn_res, 200)
  1091. assert to_string(activity.id) == id
  1092. end
  1093. test "author can reblog own private status", %{conn: conn, user: user} do
  1094. {:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
  1095. conn =
  1096. conn
  1097. |> put_req_header("content-type", "application/json")
  1098. |> post("/api/v1/statuses/#{activity.id}/reblog")
  1099. assert %{
  1100. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
  1101. "reblogged" => true,
  1102. "visibility" => "private"
  1103. } = json_response_and_validate_schema(conn, 200)
  1104. assert to_string(activity.id) == id
  1105. end
  1106. end
  1107. describe "unreblogging" do
  1108. setup do: oauth_access(["write:statuses"])
  1109. test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
  1110. activity = insert(:note_activity)
  1111. {:ok, _} = CommonAPI.repeat(activity.id, user)
  1112. conn =
  1113. conn
  1114. |> put_req_header("content-type", "application/json")
  1115. |> post("/api/v1/statuses/#{activity.id}/unreblog")
  1116. assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
  1117. json_response_and_validate_schema(conn, 200)
  1118. assert to_string(activity.id) == id
  1119. end
  1120. test "returns 404 error when activity does not exist", %{conn: conn} do
  1121. conn =
  1122. conn
  1123. |> put_req_header("content-type", "application/json")
  1124. |> post("/api/v1/statuses/foo/unreblog")
  1125. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  1126. end
  1127. end
  1128. describe "favoriting" do
  1129. setup do: oauth_access(["write:favourites"])
  1130. test "favs a status and returns it", %{conn: conn} do
  1131. activity = insert(:note_activity)
  1132. conn =
  1133. conn
  1134. |> put_req_header("content-type", "application/json")
  1135. |> post("/api/v1/statuses/#{activity.id}/favourite")
  1136. assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
  1137. json_response_and_validate_schema(conn, 200)
  1138. assert to_string(activity.id) == id
  1139. end
  1140. test "favoriting twice will just return 200", %{conn: conn} do
  1141. activity = insert(:note_activity)
  1142. conn
  1143. |> put_req_header("content-type", "application/json")
  1144. |> post("/api/v1/statuses/#{activity.id}/favourite")
  1145. assert conn
  1146. |> put_req_header("content-type", "application/json")
  1147. |> post("/api/v1/statuses/#{activity.id}/favourite")
  1148. |> json_response_and_validate_schema(200)
  1149. end
  1150. test "returns 404 error for a wrong id", %{conn: conn} do
  1151. conn =
  1152. conn
  1153. |> put_req_header("content-type", "application/json")
  1154. |> post("/api/v1/statuses/1/favourite")
  1155. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  1156. end
  1157. end
  1158. describe "unfavoriting" do
  1159. setup do: oauth_access(["write:favourites"])
  1160. test "unfavorites a status and returns it", %{user: user, conn: conn} do
  1161. activity = insert(:note_activity)
  1162. {:ok, _} = CommonAPI.favorite(activity.id, user)
  1163. conn =
  1164. conn
  1165. |> put_req_header("content-type", "application/json")
  1166. |> post("/api/v1/statuses/#{activity.id}/unfavourite")
  1167. assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
  1168. json_response_and_validate_schema(conn, 200)
  1169. assert to_string(activity.id) == id
  1170. end
  1171. test "returns 404 error for a wrong id", %{conn: conn} do
  1172. conn =
  1173. conn
  1174. |> put_req_header("content-type", "application/json")
  1175. |> post("/api/v1/statuses/1/unfavourite")
  1176. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  1177. end
  1178. end
  1179. describe "pinned statuses" do
  1180. setup do: oauth_access(["write:accounts"])
  1181. setup %{user: user} do
  1182. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
  1183. %{activity: activity}
  1184. end
  1185. setup do: clear_config([:instance, :max_pinned_statuses], 1)
  1186. test "pin status", %{conn: conn, user: user, activity: activity} do
  1187. id = activity.id
  1188. assert %{"id" => ^id, "pinned" => true} =
  1189. conn
  1190. |> put_req_header("content-type", "application/json")
  1191. |> post("/api/v1/statuses/#{activity.id}/pin")
  1192. |> json_response_and_validate_schema(200)
  1193. assert [%{"id" => ^id, "pinned" => true}] =
  1194. conn
  1195. |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
  1196. |> json_response_and_validate_schema(200)
  1197. end
  1198. test "non authenticated user", %{activity: activity} do
  1199. assert build_conn()
  1200. |> put_req_header("content-type", "application/json")
  1201. |> post("/api/v1/statuses/#{activity.id}/pin")
  1202. |> json_response(403) == %{"error" => "Invalid credentials."}
  1203. end
  1204. test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
  1205. {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
  1206. conn =
  1207. conn
  1208. |> put_req_header("content-type", "application/json")
  1209. |> post("/api/v1/statuses/#{dm.id}/pin")
  1210. assert json_response_and_validate_schema(conn, 422) == %{
  1211. "error" => "Non-public status cannot be pinned"
  1212. }
  1213. end
  1214. test "pin by another user", %{activity: activity} do
  1215. %{conn: conn} = oauth_access(["write:accounts"])
  1216. assert conn
  1217. |> put_req_header("content-type", "application/json")
  1218. |> post("/api/v1/statuses/#{activity.id}/pin")
  1219. |> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
  1220. end
  1221. test "unpin status", %{conn: conn, user: user, activity: activity} do
  1222. {:ok, _} = CommonAPI.pin(activity.id, user)
  1223. user = refresh_record(user)
  1224. id_str = to_string(activity.id)
  1225. assert %{"id" => ^id_str, "pinned" => false} =
  1226. conn
  1227. |> assign(:user, user)
  1228. |> post("/api/v1/statuses/#{activity.id}/unpin")
  1229. |> json_response_and_validate_schema(200)
  1230. assert [] =
  1231. conn
  1232. |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
  1233. |> json_response_and_validate_schema(200)
  1234. end
  1235. test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
  1236. assert conn
  1237. |> put_req_header("content-type", "application/json")
  1238. |> post("/api/v1/statuses/1/unpin")
  1239. |> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
  1240. end
  1241. test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
  1242. {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
  1243. id_str_one = to_string(activity_one.id)
  1244. assert %{"id" => ^id_str_one, "pinned" => true} =
  1245. conn
  1246. |> put_req_header("content-type", "application/json")
  1247. |> post("/api/v1/statuses/#{id_str_one}/pin")
  1248. |> json_response_and_validate_schema(200)
  1249. user = refresh_record(user)
  1250. assert %{"error" => "You have already pinned the maximum number of statuses"} =
  1251. conn
  1252. |> assign(:user, user)
  1253. |> post("/api/v1/statuses/#{activity_two.id}/pin")
  1254. |> json_response_and_validate_schema(400)
  1255. end
  1256. test "on pin removes deletion job, on unpin reschedule deletion" do
  1257. %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
  1258. expires_in = 2 * 60 * 60
  1259. expires_at = DateTime.add(DateTime.utc_now(), expires_in)
  1260. assert %{"id" => id} =
  1261. conn
  1262. |> put_req_header("content-type", "application/json")
  1263. |> post("/api/v1/statuses", %{
  1264. "status" => "oolong",
  1265. "expires_in" => expires_in
  1266. })
  1267. |> json_response_and_validate_schema(200)
  1268. assert_enqueued(
  1269. worker: Pleroma.Workers.PurgeExpiredActivity,
  1270. args: %{activity_id: id},
  1271. scheduled_at: expires_at
  1272. )
  1273. assert %{"id" => ^id, "pinned" => true} =
  1274. conn
  1275. |> put_req_header("content-type", "application/json")
  1276. |> post("/api/v1/statuses/#{id}/pin")
  1277. |> json_response_and_validate_schema(200)
  1278. refute_enqueued(
  1279. worker: Pleroma.Workers.PurgeExpiredActivity,
  1280. args: %{activity_id: id},
  1281. scheduled_at: expires_at
  1282. )
  1283. assert %{"id" => ^id, "pinned" => false} =
  1284. conn
  1285. |> put_req_header("content-type", "application/json")
  1286. |> post("/api/v1/statuses/#{id}/unpin")
  1287. |> json_response_and_validate_schema(200)
  1288. assert_enqueued(
  1289. worker: Pleroma.Workers.PurgeExpiredActivity,
  1290. args: %{activity_id: id},
  1291. scheduled_at: expires_at
  1292. )
  1293. end
  1294. end
  1295. test "bookmarks" do
  1296. bookmarks_uri = "/api/v1/bookmarks"
  1297. %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
  1298. author = insert(:user)
  1299. {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
  1300. {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
  1301. response1 =
  1302. conn
  1303. |> put_req_header("content-type", "application/json")
  1304. |> post("/api/v1/statuses/#{activity1.id}/bookmark")
  1305. assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
  1306. response2 =
  1307. conn
  1308. |> put_req_header("content-type", "application/json")
  1309. |> post("/api/v1/statuses/#{activity2.id}/bookmark")
  1310. assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
  1311. bookmarks = get(conn, bookmarks_uri)
  1312. assert [
  1313. json_response_and_validate_schema(response2, 200),
  1314. json_response_and_validate_schema(response1, 200)
  1315. ] ==
  1316. json_response_and_validate_schema(bookmarks, 200)
  1317. response1 =
  1318. conn
  1319. |> put_req_header("content-type", "application/json")
  1320. |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
  1321. assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
  1322. bookmarks = get(conn, bookmarks_uri)
  1323. assert [json_response_and_validate_schema(response2, 200)] ==
  1324. json_response_and_validate_schema(bookmarks, 200)
  1325. end
  1326. test "bookmark folders" do
  1327. %{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])
  1328. {:ok, folder} = Pleroma.BookmarkFolder.create(user.id, "folder")
  1329. author = insert(:user)
  1330. folder_bookmarks_uri = "/api/v1/bookmarks?folder_id=#{folder.id}"
  1331. {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
  1332. {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
  1333. # Add bookmark with a folder
  1334. response =
  1335. conn
  1336. |> put_req_header("content-type", "application/json")
  1337. |> post("/api/v1/statuses/#{activity1.id}/bookmark", %{folder_id: folder.id})
  1338. assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
  1339. assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
  1340. folder.id
  1341. response =
  1342. conn
  1343. |> put_req_header("content-type", "application/json")
  1344. |> post("/api/v1/statuses/#{activity2.id}/bookmark")
  1345. assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
  1346. assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] == nil
  1347. bookmarks =
  1348. get(conn, folder_bookmarks_uri)
  1349. |> json_response_and_validate_schema(200)
  1350. assert length(bookmarks) == 1
  1351. # Update folder for existing bookmark
  1352. response =
  1353. conn
  1354. |> put_req_header("content-type", "application/json")
  1355. |> post("/api/v1/statuses/#{activity2.id}/bookmark", %{folder_id: folder.id})
  1356. assert json_response_and_validate_schema(response, 200)["bookmarked"] == true
  1357. assert json_response_and_validate_schema(response, 200)["pleroma"]["bookmark_folder"] ==
  1358. folder.id
  1359. bookmarks =
  1360. get(conn, folder_bookmarks_uri)
  1361. |> json_response_and_validate_schema(200)
  1362. assert length(bookmarks) == 2
  1363. end
  1364. describe "conversation muting" do
  1365. setup do: oauth_access(["write:mutes"])
  1366. setup do
  1367. post_user = insert(:user)
  1368. {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
  1369. %{activity: activity}
  1370. end
  1371. test "mute conversation", %{conn: conn, activity: activity} do
  1372. id_str = to_string(activity.id)
  1373. assert %{"id" => ^id_str, "muted" => true} =
  1374. conn
  1375. |> put_req_header("content-type", "application/json")
  1376. |> post("/api/v1/statuses/#{activity.id}/mute")
  1377. |> json_response_and_validate_schema(200)
  1378. end
  1379. test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
  1380. {:ok, _} = CommonAPI.add_mute(activity, user)
  1381. conn =
  1382. conn
  1383. |> put_req_header("content-type", "application/json")
  1384. |> post("/api/v1/statuses/#{activity.id}/mute")
  1385. assert json_response_and_validate_schema(conn, 400) == %{
  1386. "error" => "conversation is already muted"
  1387. }
  1388. end
  1389. test "unmute conversation", %{conn: conn, user: user, activity: activity} do
  1390. {:ok, _} = CommonAPI.add_mute(activity, user)
  1391. id_str = to_string(activity.id)
  1392. assert %{"id" => ^id_str, "muted" => false} =
  1393. conn
  1394. # |> assign(:user, user)
  1395. |> post("/api/v1/statuses/#{activity.id}/unmute")
  1396. |> json_response_and_validate_schema(200)
  1397. end
  1398. end
  1399. test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
  1400. user1 = insert(:user)
  1401. user2 = insert(:user)
  1402. user3 = insert(:user)
  1403. {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
  1404. # Reply to status from another user
  1405. conn1 =
  1406. conn
  1407. |> assign(:user, user2)
  1408. |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
  1409. |> put_req_header("content-type", "application/json")
  1410. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
  1411. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
  1412. activity = Activity.get_by_id_with_object(id)
  1413. assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
  1414. Object.normalize(replied_to, fetch: false).data["id"]
  1415. assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
  1416. # Reblog from the third user
  1417. conn2 =
  1418. conn
  1419. |> assign(:user, user3)
  1420. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
  1421. |> put_req_header("content-type", "application/json")
  1422. |> post("/api/v1/statuses/#{activity.id}/reblog")
  1423. assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
  1424. json_response_and_validate_schema(conn2, 200)
  1425. assert to_string(activity.id) == id
  1426. # Getting third user status
  1427. conn3 =
  1428. conn
  1429. |> assign(:user, user3)
  1430. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
  1431. |> get("/api/v1/timelines/home")
  1432. [reblogged_activity] = json_response_and_validate_schema(conn3, 200)
  1433. assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
  1434. replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
  1435. assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
  1436. end
  1437. describe "GET /api/v1/statuses/:id/favourited_by" do
  1438. setup do: oauth_access(["read:accounts"])
  1439. setup %{user: user} do
  1440. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1441. %{activity: activity}
  1442. end
  1443. test "returns users who have favorited the status", %{conn: conn, activity: activity} do
  1444. other_user = insert(:user)
  1445. {:ok, _} = CommonAPI.favorite(activity.id, other_user)
  1446. response =
  1447. conn
  1448. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1449. |> json_response_and_validate_schema(:ok)
  1450. [%{"id" => id}] = response
  1451. assert id == other_user.id
  1452. end
  1453. test "returns empty array when status has not been favorited yet", %{
  1454. conn: conn,
  1455. activity: activity
  1456. } do
  1457. response =
  1458. conn
  1459. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1460. |> json_response_and_validate_schema(:ok)
  1461. assert Enum.empty?(response)
  1462. end
  1463. test "does not return users who have favorited the status but are blocked", %{
  1464. conn: %{assigns: %{user: user}} = conn,
  1465. activity: activity
  1466. } do
  1467. other_user = insert(:user)
  1468. {:ok, _user_relationship} = User.block(user, other_user)
  1469. {:ok, _} = CommonAPI.favorite(activity.id, other_user)
  1470. response =
  1471. conn
  1472. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1473. |> json_response_and_validate_schema(:ok)
  1474. assert Enum.empty?(response)
  1475. end
  1476. test "does not fail on an unauthenticated request", %{activity: activity} do
  1477. other_user = insert(:user)
  1478. {:ok, _} = CommonAPI.favorite(activity.id, other_user)
  1479. response =
  1480. build_conn()
  1481. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1482. |> json_response_and_validate_schema(:ok)
  1483. [%{"id" => id}] = response
  1484. assert id == other_user.id
  1485. end
  1486. test "requires authentication for private posts", %{user: user} do
  1487. other_user = insert(:user)
  1488. {:ok, activity} =
  1489. CommonAPI.post(user, %{
  1490. status: "@#{other_user.nickname} wanna get some #cofe together?",
  1491. visibility: "direct"
  1492. })
  1493. {:ok, _} = CommonAPI.favorite(activity.id, other_user)
  1494. favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
  1495. build_conn()
  1496. |> get(favourited_by_url)
  1497. |> json_response_and_validate_schema(404)
  1498. conn =
  1499. build_conn()
  1500. |> assign(:user, other_user)
  1501. |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
  1502. conn
  1503. |> assign(:token, nil)
  1504. |> get(favourited_by_url)
  1505. |> json_response_and_validate_schema(404)
  1506. response =
  1507. conn
  1508. |> get(favourited_by_url)
  1509. |> json_response_and_validate_schema(200)
  1510. [%{"id" => id}] = response
  1511. assert id == other_user.id
  1512. end
  1513. test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
  1514. clear_config([:instance, :show_reactions], false)
  1515. other_user = insert(:user)
  1516. {:ok, _} = CommonAPI.favorite(activity.id, other_user)
  1517. response =
  1518. conn
  1519. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1520. |> json_response_and_validate_schema(:ok)
  1521. assert Enum.empty?(response)
  1522. end
  1523. end
  1524. describe "GET /api/v1/statuses/:id/reblogged_by" do
  1525. setup do: oauth_access(["read:accounts"])
  1526. setup %{user: user} do
  1527. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1528. %{activity: activity}
  1529. end
  1530. test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
  1531. other_user = insert(:user)
  1532. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1533. response =
  1534. conn
  1535. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1536. |> json_response_and_validate_schema(:ok)
  1537. [%{"id" => id}] = response
  1538. assert id == other_user.id
  1539. end
  1540. test "returns empty array when status has not been reblogged yet", %{
  1541. conn: conn,
  1542. activity: activity
  1543. } do
  1544. response =
  1545. conn
  1546. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1547. |> json_response_and_validate_schema(:ok)
  1548. assert Enum.empty?(response)
  1549. end
  1550. test "does not return users who have reblogged the status but are blocked", %{
  1551. conn: %{assigns: %{user: user}} = conn,
  1552. activity: activity
  1553. } do
  1554. other_user = insert(:user)
  1555. {:ok, _user_relationship} = User.block(user, other_user)
  1556. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1557. response =
  1558. conn
  1559. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1560. |> json_response_and_validate_schema(:ok)
  1561. assert Enum.empty?(response)
  1562. end
  1563. test "does not return users who have reblogged the status privately", %{
  1564. conn: conn
  1565. } do
  1566. other_user = insert(:user)
  1567. {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
  1568. {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
  1569. response =
  1570. conn
  1571. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1572. |> json_response_and_validate_schema(:ok)
  1573. assert Enum.empty?(response)
  1574. end
  1575. test "does not fail on an unauthenticated request", %{activity: activity} do
  1576. other_user = insert(:user)
  1577. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1578. response =
  1579. build_conn()
  1580. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1581. |> json_response_and_validate_schema(:ok)
  1582. [%{"id" => id}] = response
  1583. assert id == other_user.id
  1584. end
  1585. test "requires authentication for private posts", %{user: user} do
  1586. other_user = insert(:user)
  1587. {:ok, activity} =
  1588. CommonAPI.post(user, %{
  1589. status: "@#{other_user.nickname} wanna get some #cofe together?",
  1590. visibility: "direct"
  1591. })
  1592. build_conn()
  1593. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1594. |> json_response_and_validate_schema(404)
  1595. response =
  1596. build_conn()
  1597. |> assign(:user, other_user)
  1598. |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
  1599. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1600. |> json_response_and_validate_schema(200)
  1601. assert [] == response
  1602. end
  1603. end
  1604. test "context" do
  1605. user = insert(:user)
  1606. {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
  1607. {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
  1608. {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
  1609. {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
  1610. {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
  1611. response =
  1612. build_conn()
  1613. |> get("/api/v1/statuses/#{id3}/context")
  1614. |> json_response_and_validate_schema(:ok)
  1615. assert %{
  1616. "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
  1617. "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
  1618. } = response
  1619. end
  1620. test "favorites paginate correctly" do
  1621. %{user: user, conn: conn} = oauth_access(["read:favourites"])
  1622. other_user = insert(:user)
  1623. {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
  1624. {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
  1625. {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
  1626. {:ok, _first_favorite} = CommonAPI.favorite(third_post.id, user)
  1627. {:ok, _second_favorite} = CommonAPI.favorite(first_post.id, user)
  1628. {:ok, third_favorite} = CommonAPI.favorite(second_post.id, user)
  1629. result =
  1630. conn
  1631. |> get("/api/v1/favourites?limit=1")
  1632. assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
  1633. assert post_id == second_post.id
  1634. # Using the header for pagination works correctly
  1635. [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
  1636. [next_url, _next_rel] = String.split(next, ";")
  1637. next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<")
  1638. max_id = Helpers.get_query_parameter(next_url, "max_id")
  1639. assert max_id == third_favorite.id
  1640. result =
  1641. conn
  1642. |> get("/api/v1/favourites?max_id=#{max_id}")
  1643. assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
  1644. json_response_and_validate_schema(result, 200)
  1645. assert first_post_id == first_post.id
  1646. assert third_post_id == third_post.id
  1647. end
  1648. test "returns the favorites of a user" do
  1649. %{user: user, conn: conn} = oauth_access(["read:favourites"])
  1650. other_user = insert(:user)
  1651. {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
  1652. {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
  1653. {:ok, last_like} = CommonAPI.favorite(activity.id, user)
  1654. first_conn = get(conn, "/api/v1/favourites")
  1655. assert [status] = json_response_and_validate_schema(first_conn, 200)
  1656. assert status["id"] == to_string(activity.id)
  1657. assert [{"link", _link_header}] =
  1658. Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
  1659. # Honours query params
  1660. {:ok, second_activity} =
  1661. CommonAPI.post(other_user, %{
  1662. status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
  1663. })
  1664. {:ok, _} = CommonAPI.favorite(second_activity.id, user)
  1665. second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
  1666. assert [second_status] = json_response_and_validate_schema(second_conn, 200)
  1667. assert second_status["id"] == to_string(second_activity.id)
  1668. third_conn = get(conn, "/api/v1/favourites?limit=0")
  1669. assert [] = json_response_and_validate_schema(third_conn, 200)
  1670. end
  1671. test "expires_at is nil for another user" do
  1672. %{conn: conn, user: user} = oauth_access(["read:statuses"])
  1673. expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
  1674. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
  1675. assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
  1676. conn
  1677. |> get("/api/v1/statuses/#{activity.id}")
  1678. |> json_response_and_validate_schema(:ok)
  1679. {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
  1680. assert DateTime.diff(expires_at, a_expires_at) == 0
  1681. %{conn: conn} = oauth_access(["read:statuses"])
  1682. assert %{"pleroma" => %{"expires_at" => nil}} =
  1683. conn
  1684. |> get("/api/v1/statuses/#{activity.id}")
  1685. |> json_response_and_validate_schema(:ok)
  1686. end
  1687. describe "local-only statuses" do
  1688. test "posting a local only status" do
  1689. %{user: _user, conn: conn} = oauth_access(["write:statuses"])
  1690. conn_one =
  1691. conn
  1692. |> put_req_header("content-type", "application/json")
  1693. |> post("/api/v1/statuses", %{
  1694. "status" => "cofe",
  1695. "visibility" => "local"
  1696. })
  1697. local = Utils.as_local_public()
  1698. assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
  1699. json_response_and_validate_schema(conn_one, 200)
  1700. assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
  1701. end
  1702. test "other users can read local-only posts" do
  1703. user = insert(:user)
  1704. %{user: _reader, conn: conn} = oauth_access(["read:statuses"])
  1705. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  1706. received =
  1707. conn
  1708. |> get("/api/v1/statuses/#{activity.id}")
  1709. |> json_response_and_validate_schema(:ok)
  1710. assert received["id"] == activity.id
  1711. end
  1712. test "anonymous users cannot see local-only posts" do
  1713. user = insert(:user)
  1714. {:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
  1715. _received =
  1716. build_conn()
  1717. |> get("/api/v1/statuses/#{activity.id}")
  1718. |> json_response_and_validate_schema(:not_found)
  1719. end
  1720. end
  1721. describe "muted reactions" do
  1722. test "index" do
  1723. %{conn: conn, user: user} = oauth_access(["read:statuses"])
  1724. other_user = insert(:user)
  1725. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1726. {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
  1727. User.mute(user, other_user)
  1728. result =
  1729. conn
  1730. |> get("/api/v1/statuses/?id[]=#{activity.id}")
  1731. |> json_response_and_validate_schema(200)
  1732. assert [
  1733. %{
  1734. "pleroma" => %{
  1735. "emoji_reactions" => []
  1736. }
  1737. }
  1738. ] = result
  1739. result =
  1740. conn
  1741. |> get("/api/v1/statuses/?id[]=#{activity.id}&with_muted=true")
  1742. |> json_response_and_validate_schema(200)
  1743. assert [
  1744. %{
  1745. "pleroma" => %{
  1746. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  1747. }
  1748. }
  1749. ] = result
  1750. end
  1751. test "show" do
  1752. # %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
  1753. %{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
  1754. other_user = insert(:user)
  1755. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1756. {:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
  1757. User.mute(user, other_user)
  1758. result =
  1759. conn
  1760. |> get("/api/v1/statuses/#{activity.id}")
  1761. |> json_response_and_validate_schema(200)
  1762. assert %{
  1763. "pleroma" => %{
  1764. "emoji_reactions" => []
  1765. }
  1766. } = result
  1767. result =
  1768. conn
  1769. |> get("/api/v1/statuses/#{activity.id}?with_muted=true")
  1770. |> json_response_and_validate_schema(200)
  1771. assert %{
  1772. "pleroma" => %{
  1773. "emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
  1774. }
  1775. } = result
  1776. end
  1777. end
  1778. describe "get status history" do
  1779. setup do
  1780. %{conn: build_conn()}
  1781. end
  1782. test "unedited post", %{conn: conn} do
  1783. activity = insert(:note_activity)
  1784. conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
  1785. assert [_] = json_response_and_validate_schema(conn, 200)
  1786. end
  1787. test "edited post", %{conn: conn} do
  1788. note =
  1789. insert(
  1790. :note,
  1791. data: %{
  1792. "formerRepresentations" => %{
  1793. "type" => "OrderedCollection",
  1794. "orderedItems" => [
  1795. %{
  1796. "type" => "Note",
  1797. "content" => "mew mew 2",
  1798. "summary" => "title 2"
  1799. },
  1800. %{
  1801. "type" => "Note",
  1802. "content" => "mew mew 1",
  1803. "summary" => "title 1"
  1804. }
  1805. ],
  1806. "totalItems" => 2
  1807. }
  1808. }
  1809. )
  1810. activity = insert(:note_activity, note: note)
  1811. conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
  1812. assert [%{"spoiler_text" => "title 1"}, %{"spoiler_text" => "title 2"}, _] =
  1813. json_response_and_validate_schema(conn, 200)
  1814. end
  1815. end
  1816. describe "get status source" do
  1817. setup do
  1818. %{conn: build_conn()}
  1819. end
  1820. test "it returns the source", %{conn: conn} do
  1821. user = insert(:user)
  1822. {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
  1823. conn = get(conn, "/api/v1/statuses/#{activity.id}/source")
  1824. id = activity.id
  1825. assert %{"id" => ^id, "text" => "mew mew #abc", "spoiler_text" => "#def"} =
  1826. json_response_and_validate_schema(conn, 200)
  1827. end
  1828. end
  1829. describe "update status" do
  1830. setup do
  1831. oauth_access(["write:statuses"])
  1832. end
  1833. test "it updates the status" do
  1834. %{conn: conn, user: user} = oauth_access(["write:statuses", "read:statuses"])
  1835. {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
  1836. conn
  1837. |> get("/api/v1/statuses/#{activity.id}")
  1838. |> json_response_and_validate_schema(200)
  1839. response =
  1840. conn
  1841. |> put_req_header("content-type", "application/json")
  1842. |> put("/api/v1/statuses/#{activity.id}", %{
  1843. "status" => "edited",
  1844. "spoiler_text" => "lol"
  1845. })
  1846. |> json_response_and_validate_schema(200)
  1847. assert response["content"] == "edited"
  1848. assert response["spoiler_text"] == "lol"
  1849. response =
  1850. conn
  1851. |> get("/api/v1/statuses/#{activity.id}")
  1852. |> json_response_and_validate_schema(200)
  1853. assert response["content"] == "edited"
  1854. assert response["spoiler_text"] == "lol"
  1855. end
  1856. test "it updates the attachments", %{conn: conn, user: user} do
  1857. attachment = insert(:attachment, user: user)
  1858. attachment_id = to_string(attachment.id)
  1859. {:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
  1860. response =
  1861. conn
  1862. |> put_req_header("content-type", "application/json")
  1863. |> put("/api/v1/statuses/#{activity.id}", %{
  1864. "status" => "mew mew #abc",
  1865. "spoiler_text" => "#def",
  1866. "media_ids" => [attachment_id]
  1867. })
  1868. |> json_response_and_validate_schema(200)
  1869. assert [%{"id" => ^attachment_id}] = response["media_attachments"]
  1870. end
  1871. test "it does not update visibility", %{conn: conn, user: user} do
  1872. {:ok, activity} =
  1873. CommonAPI.post(user, %{
  1874. status: "mew mew #abc",
  1875. spoiler_text: "#def",
  1876. visibility: "private"
  1877. })
  1878. response =
  1879. conn
  1880. |> put_req_header("content-type", "application/json")
  1881. |> put("/api/v1/statuses/#{activity.id}", %{
  1882. "status" => "edited",
  1883. "spoiler_text" => "lol"
  1884. })
  1885. |> json_response_and_validate_schema(200)
  1886. assert response["visibility"] == "private"
  1887. end
  1888. test "it refuses to update when original post is not by the user", %{conn: conn} do
  1889. another_user = insert(:user)
  1890. {:ok, activity} =
  1891. CommonAPI.post(another_user, %{status: "mew mew #abc", spoiler_text: "#def"})
  1892. conn
  1893. |> put_req_header("content-type", "application/json")
  1894. |> put("/api/v1/statuses/#{activity.id}", %{
  1895. "status" => "edited",
  1896. "spoiler_text" => "lol"
  1897. })
  1898. |> json_response_and_validate_schema(:forbidden)
  1899. end
  1900. test "it returns 404 if the user cannot see the post", %{conn: conn} do
  1901. another_user = insert(:user)
  1902. {:ok, activity} =
  1903. CommonAPI.post(another_user, %{
  1904. status: "mew mew #abc",
  1905. spoiler_text: "#def",
  1906. visibility: "private"
  1907. })
  1908. conn
  1909. |> put_req_header("content-type", "application/json")
  1910. |> put("/api/v1/statuses/#{activity.id}", %{
  1911. "status" => "edited",
  1912. "spoiler_text" => "lol"
  1913. })
  1914. |> json_response_and_validate_schema(:not_found)
  1915. end
  1916. end
  1917. end