logo

pleroma

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

status_controller_test.exs (83140B)


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