logo

pleroma

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

status_controller_test.exs (58138B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
  5. use Pleroma.Web.ConnCase
  6. use Oban.Testing, repo: Pleroma.Repo
  7. alias Pleroma.Activity
  8. alias Pleroma.Config
  9. alias Pleroma.Conversation.Participation
  10. alias Pleroma.Object
  11. alias Pleroma.Repo
  12. alias Pleroma.ScheduledActivity
  13. alias Pleroma.Tests.ObanHelpers
  14. alias Pleroma.User
  15. alias Pleroma.Web.ActivityPub.ActivityPub
  16. alias Pleroma.Web.CommonAPI
  17. import Pleroma.Factory
  18. setup do: clear_config([:instance, :federating])
  19. setup do: clear_config([:instance, :allow_relay])
  20. setup do: clear_config([:rich_media, :enabled])
  21. setup do: clear_config([:mrf, :policies])
  22. setup do: clear_config([:mrf_keyword, :reject])
  23. describe "posting statuses" do
  24. setup do: oauth_access(["write:statuses"])
  25. test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
  26. Config.put([:instance, :federating], true)
  27. Config.get([:instance, :allow_relay], true)
  28. response =
  29. conn
  30. |> put_req_header("content-type", "application/json")
  31. |> post("api/v1/statuses", %{
  32. "content_type" => "text/plain",
  33. "source" => "Pleroma FE",
  34. "status" => "Hello world",
  35. "visibility" => "public"
  36. })
  37. |> json_response_and_validate_schema(200)
  38. assert response["reblogs_count"] == 0
  39. ObanHelpers.perform_all()
  40. response =
  41. conn
  42. |> get("api/v1/statuses/#{response["id"]}", %{})
  43. |> json_response_and_validate_schema(200)
  44. assert response["reblogs_count"] == 0
  45. end
  46. test "posting a status", %{conn: conn} do
  47. idempotency_key = "Pikachu rocks!"
  48. conn_one =
  49. conn
  50. |> put_req_header("content-type", "application/json")
  51. |> put_req_header("idempotency-key", idempotency_key)
  52. |> post("/api/v1/statuses", %{
  53. "status" => "cofe",
  54. "spoiler_text" => "2hu",
  55. "sensitive" => "0"
  56. })
  57. {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
  58. # Six hours
  59. assert ttl > :timer.seconds(6 * 60 * 60 - 1)
  60. assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
  61. json_response_and_validate_schema(conn_one, 200)
  62. assert Activity.get_by_id(id)
  63. conn_two =
  64. conn
  65. |> put_req_header("content-type", "application/json")
  66. |> put_req_header("idempotency-key", idempotency_key)
  67. |> post("/api/v1/statuses", %{
  68. "status" => "cofe",
  69. "spoiler_text" => "2hu",
  70. "sensitive" => 0
  71. })
  72. assert %{"id" => second_id} = json_response(conn_two, 200)
  73. assert id == second_id
  74. conn_three =
  75. conn
  76. |> put_req_header("content-type", "application/json")
  77. |> post("/api/v1/statuses", %{
  78. "status" => "cofe",
  79. "spoiler_text" => "2hu",
  80. "sensitive" => "False"
  81. })
  82. assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
  83. refute id == third_id
  84. # An activity that will expire:
  85. # 2 hours
  86. expires_in = 2 * 60 * 60
  87. expires_at = DateTime.add(DateTime.utc_now(), expires_in)
  88. conn_four =
  89. conn
  90. |> put_req_header("content-type", "application/json")
  91. |> post("api/v1/statuses", %{
  92. "status" => "oolong",
  93. "expires_in" => expires_in
  94. })
  95. assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
  96. assert Activity.get_by_id(fourth_id)
  97. assert_enqueued(
  98. worker: Pleroma.Workers.PurgeExpiredActivity,
  99. args: %{activity_id: fourth_id},
  100. scheduled_at: expires_at
  101. )
  102. end
  103. test "it fails to create a status if `expires_in` is less or equal than an hour", %{
  104. conn: conn
  105. } do
  106. # 1 minute
  107. expires_in = 1 * 60
  108. assert %{"error" => "Expiry date is too soon"} =
  109. conn
  110. |> put_req_header("content-type", "application/json")
  111. |> post("api/v1/statuses", %{
  112. "status" => "oolong",
  113. "expires_in" => expires_in
  114. })
  115. |> json_response_and_validate_schema(422)
  116. # 5 minutes
  117. expires_in = 5 * 60
  118. assert %{"error" => "Expiry date is too soon"} =
  119. conn
  120. |> put_req_header("content-type", "application/json")
  121. |> post("api/v1/statuses", %{
  122. "status" => "oolong",
  123. "expires_in" => expires_in
  124. })
  125. |> json_response_and_validate_schema(422)
  126. end
  127. test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
  128. Config.put([:mrf_keyword, :reject], ["GNO"])
  129. Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
  130. assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
  131. conn
  132. |> put_req_header("content-type", "application/json")
  133. |> post("api/v1/statuses", %{"status" => "GNO/Linux"})
  134. |> json_response_and_validate_schema(422)
  135. end
  136. test "posting an undefined status with an attachment", %{user: user, conn: conn} do
  137. file = %Plug.Upload{
  138. content_type: "image/jpg",
  139. path: Path.absname("test/fixtures/image.jpg"),
  140. filename: "an_image.jpg"
  141. }
  142. {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
  143. conn =
  144. conn
  145. |> put_req_header("content-type", "application/json")
  146. |> post("/api/v1/statuses", %{
  147. "media_ids" => [to_string(upload.id)]
  148. })
  149. assert json_response_and_validate_schema(conn, 200)
  150. end
  151. test "replying to a status", %{user: user, conn: conn} do
  152. {:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
  153. conn =
  154. conn
  155. |> put_req_header("content-type", "application/json")
  156. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
  157. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
  158. activity = Activity.get_by_id(id)
  159. assert activity.data["context"] == replied_to.data["context"]
  160. assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
  161. end
  162. test "replying to a direct message with visibility other than direct", %{
  163. user: user,
  164. conn: conn
  165. } do
  166. {:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
  167. Enum.each(["public", "private", "unlisted"], fn visibility ->
  168. conn =
  169. conn
  170. |> put_req_header("content-type", "application/json")
  171. |> post("/api/v1/statuses", %{
  172. "status" => "@#{user.nickname} hey",
  173. "in_reply_to_id" => replied_to.id,
  174. "visibility" => visibility
  175. })
  176. assert json_response_and_validate_schema(conn, 422) == %{
  177. "error" => "The message visibility must be direct"
  178. }
  179. end)
  180. end
  181. test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
  182. conn =
  183. conn
  184. |> put_req_header("content-type", "application/json")
  185. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
  186. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
  187. assert Activity.get_by_id(id)
  188. end
  189. test "posting a sensitive status", %{conn: conn} do
  190. conn =
  191. conn
  192. |> put_req_header("content-type", "application/json")
  193. |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
  194. assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
  195. json_response_and_validate_schema(conn, 200)
  196. assert Activity.get_by_id(id)
  197. end
  198. test "posting a fake status", %{conn: conn} do
  199. real_conn =
  200. conn
  201. |> put_req_header("content-type", "application/json")
  202. |> post("/api/v1/statuses", %{
  203. "status" =>
  204. "\"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"
  205. })
  206. real_status = json_response_and_validate_schema(real_conn, 200)
  207. assert real_status
  208. assert Object.get_by_ap_id(real_status["uri"])
  209. real_status =
  210. real_status
  211. |> Map.put("id", nil)
  212. |> Map.put("url", nil)
  213. |> Map.put("uri", nil)
  214. |> Map.put("created_at", nil)
  215. |> Kernel.put_in(["pleroma", "conversation_id"], nil)
  216. fake_conn =
  217. conn
  218. |> put_req_header("content-type", "application/json")
  219. |> post("/api/v1/statuses", %{
  220. "status" =>
  221. "\"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",
  222. "preview" => true
  223. })
  224. fake_status = json_response_and_validate_schema(fake_conn, 200)
  225. assert fake_status
  226. refute Object.get_by_ap_id(fake_status["uri"])
  227. fake_status =
  228. fake_status
  229. |> Map.put("id", nil)
  230. |> Map.put("url", nil)
  231. |> Map.put("uri", nil)
  232. |> Map.put("created_at", nil)
  233. |> Kernel.put_in(["pleroma", "conversation_id"], nil)
  234. assert real_status == fake_status
  235. end
  236. test "fake statuses' preview card is not cached", %{conn: conn} do
  237. clear_config([:rich_media, :enabled], true)
  238. Tesla.Mock.mock(fn
  239. %{
  240. method: :get,
  241. url: "https://example.com/twitter-card"
  242. } ->
  243. %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
  244. env ->
  245. apply(HttpRequestMock, :request, [env])
  246. end)
  247. conn1 =
  248. conn
  249. |> put_req_header("content-type", "application/json")
  250. |> post("/api/v1/statuses", %{
  251. "status" => "https://example.com/ogp",
  252. "preview" => true
  253. })
  254. conn2 =
  255. conn
  256. |> put_req_header("content-type", "application/json")
  257. |> post("/api/v1/statuses", %{
  258. "status" => "https://example.com/twitter-card",
  259. "preview" => true
  260. })
  261. assert %{"card" => %{"title" => "The Rock"}} = json_response_and_validate_schema(conn1, 200)
  262. assert %{"card" => %{"title" => "Small Island Developing States Photo Submission"}} =
  263. json_response_and_validate_schema(conn2, 200)
  264. end
  265. test "posting a status with OGP link preview", %{conn: conn} do
  266. Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
  267. clear_config([:rich_media, :enabled], true)
  268. conn =
  269. conn
  270. |> put_req_header("content-type", "application/json")
  271. |> post("/api/v1/statuses", %{
  272. "status" => "https://example.com/ogp"
  273. })
  274. assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
  275. json_response_and_validate_schema(conn, 200)
  276. assert Activity.get_by_id(id)
  277. end
  278. test "posting a direct status", %{conn: conn} do
  279. user2 = insert(:user)
  280. content = "direct cofe @#{user2.nickname}"
  281. conn =
  282. conn
  283. |> put_req_header("content-type", "application/json")
  284. |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
  285. assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
  286. assert response["visibility"] == "direct"
  287. assert response["pleroma"]["direct_conversation_id"]
  288. assert activity = Activity.get_by_id(id)
  289. assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
  290. assert activity.data["to"] == [user2.ap_id]
  291. assert activity.data["cc"] == []
  292. end
  293. end
  294. describe "posting scheduled statuses" do
  295. setup do: oauth_access(["write:statuses"])
  296. test "creates a scheduled activity", %{conn: conn} do
  297. scheduled_at =
  298. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
  299. |> NaiveDateTime.to_iso8601()
  300. |> Kernel.<>("Z")
  301. conn =
  302. conn
  303. |> put_req_header("content-type", "application/json")
  304. |> post("/api/v1/statuses", %{
  305. "status" => "scheduled",
  306. "scheduled_at" => scheduled_at
  307. })
  308. assert %{"scheduled_at" => expected_scheduled_at} =
  309. json_response_and_validate_schema(conn, 200)
  310. assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
  311. assert [] == Repo.all(Activity)
  312. end
  313. test "ignores nil values", %{conn: conn} do
  314. conn =
  315. conn
  316. |> put_req_header("content-type", "application/json")
  317. |> post("/api/v1/statuses", %{
  318. "status" => "not scheduled",
  319. "scheduled_at" => nil
  320. })
  321. assert result = json_response_and_validate_schema(conn, 200)
  322. assert Activity.get_by_id(result["id"])
  323. end
  324. test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
  325. scheduled_at =
  326. NaiveDateTime.utc_now()
  327. |> NaiveDateTime.add(:timer.minutes(120), :millisecond)
  328. |> NaiveDateTime.to_iso8601()
  329. |> Kernel.<>("Z")
  330. file = %Plug.Upload{
  331. content_type: "image/jpg",
  332. path: Path.absname("test/fixtures/image.jpg"),
  333. filename: "an_image.jpg"
  334. }
  335. {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
  336. conn =
  337. conn
  338. |> put_req_header("content-type", "application/json")
  339. |> post("/api/v1/statuses", %{
  340. "media_ids" => [to_string(upload.id)],
  341. "status" => "scheduled",
  342. "scheduled_at" => scheduled_at
  343. })
  344. assert %{"media_attachments" => [media_attachment]} =
  345. json_response_and_validate_schema(conn, 200)
  346. assert %{"type" => "image"} = media_attachment
  347. end
  348. test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
  349. %{conn: conn} do
  350. scheduled_at =
  351. NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
  352. |> NaiveDateTime.to_iso8601()
  353. |> Kernel.<>("Z")
  354. conn =
  355. conn
  356. |> put_req_header("content-type", "application/json")
  357. |> post("/api/v1/statuses", %{
  358. "status" => "not scheduled",
  359. "scheduled_at" => scheduled_at
  360. })
  361. assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
  362. assert [] == Repo.all(ScheduledActivity)
  363. end
  364. test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
  365. today =
  366. NaiveDateTime.utc_now()
  367. |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
  368. |> NaiveDateTime.to_iso8601()
  369. # TODO
  370. |> Kernel.<>("Z")
  371. attrs = %{params: %{}, scheduled_at: today}
  372. {:ok, _} = ScheduledActivity.create(user, attrs)
  373. {:ok, _} = ScheduledActivity.create(user, attrs)
  374. conn =
  375. conn
  376. |> put_req_header("content-type", "application/json")
  377. |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
  378. assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
  379. end
  380. test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
  381. today =
  382. NaiveDateTime.utc_now()
  383. |> NaiveDateTime.add(:timer.minutes(6), :millisecond)
  384. |> NaiveDateTime.to_iso8601()
  385. |> Kernel.<>("Z")
  386. tomorrow =
  387. NaiveDateTime.utc_now()
  388. |> NaiveDateTime.add(:timer.hours(36), :millisecond)
  389. |> NaiveDateTime.to_iso8601()
  390. |> Kernel.<>("Z")
  391. attrs = %{params: %{}, scheduled_at: today}
  392. {:ok, _} = ScheduledActivity.create(user, attrs)
  393. {:ok, _} = ScheduledActivity.create(user, attrs)
  394. {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
  395. conn =
  396. conn
  397. |> put_req_header("content-type", "application/json")
  398. |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
  399. assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
  400. end
  401. end
  402. describe "posting polls" do
  403. setup do: oauth_access(["write:statuses"])
  404. test "posting a poll", %{conn: conn} do
  405. time = NaiveDateTime.utc_now()
  406. conn =
  407. conn
  408. |> put_req_header("content-type", "application/json")
  409. |> post("/api/v1/statuses", %{
  410. "status" => "Who is the #bestgrill?",
  411. "poll" => %{
  412. "options" => ["Rei", "Asuka", "Misato"],
  413. "expires_in" => 420
  414. }
  415. })
  416. response = json_response_and_validate_schema(conn, 200)
  417. assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
  418. title in ["Rei", "Asuka", "Misato"]
  419. end)
  420. assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
  421. refute response["poll"]["expred"]
  422. question = Object.get_by_id(response["poll"]["id"])
  423. # closed contains utc timezone
  424. assert question.data["closed"] =~ "Z"
  425. end
  426. test "option limit is enforced", %{conn: conn} do
  427. limit = Config.get([:instance, :poll_limits, :max_options])
  428. conn =
  429. conn
  430. |> put_req_header("content-type", "application/json")
  431. |> post("/api/v1/statuses", %{
  432. "status" => "desu~",
  433. "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
  434. })
  435. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  436. assert error == "Poll can't contain more than #{limit} options"
  437. end
  438. test "option character limit is enforced", %{conn: conn} do
  439. limit = Config.get([:instance, :poll_limits, :max_option_chars])
  440. conn =
  441. conn
  442. |> put_req_header("content-type", "application/json")
  443. |> post("/api/v1/statuses", %{
  444. "status" => "...",
  445. "poll" => %{
  446. "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
  447. "expires_in" => 1
  448. }
  449. })
  450. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  451. assert error == "Poll options cannot be longer than #{limit} characters each"
  452. end
  453. test "minimal date limit is enforced", %{conn: conn} do
  454. limit = Config.get([:instance, :poll_limits, :min_expiration])
  455. conn =
  456. conn
  457. |> put_req_header("content-type", "application/json")
  458. |> post("/api/v1/statuses", %{
  459. "status" => "imagine arbitrary limits",
  460. "poll" => %{
  461. "options" => ["this post was made by pleroma gang"],
  462. "expires_in" => limit - 1
  463. }
  464. })
  465. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  466. assert error == "Expiration date is too soon"
  467. end
  468. test "maximum date limit is enforced", %{conn: conn} do
  469. limit = Config.get([:instance, :poll_limits, :max_expiration])
  470. conn =
  471. conn
  472. |> put_req_header("content-type", "application/json")
  473. |> post("/api/v1/statuses", %{
  474. "status" => "imagine arbitrary limits",
  475. "poll" => %{
  476. "options" => ["this post was made by pleroma gang"],
  477. "expires_in" => limit + 1
  478. }
  479. })
  480. %{"error" => error} = json_response_and_validate_schema(conn, 422)
  481. assert error == "Expiration date is too far in the future"
  482. end
  483. end
  484. test "get a status" do
  485. %{conn: conn} = oauth_access(["read:statuses"])
  486. activity = insert(:note_activity)
  487. conn = get(conn, "/api/v1/statuses/#{activity.id}")
  488. assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
  489. assert id == to_string(activity.id)
  490. end
  491. defp local_and_remote_activities do
  492. local = insert(:note_activity)
  493. remote = insert(:note_activity, local: false)
  494. {:ok, local: local, remote: remote}
  495. end
  496. describe "status with restrict unauthenticated activities for local and remote" do
  497. setup do: local_and_remote_activities()
  498. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  499. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  500. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  501. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  502. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  503. "error" => "Record not found"
  504. }
  505. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  506. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  507. "error" => "Record not found"
  508. }
  509. end
  510. test "if user is authenticated", %{local: local, remote: remote} do
  511. %{conn: conn} = oauth_access(["read"])
  512. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  513. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  514. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  515. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  516. end
  517. end
  518. describe "status with restrict unauthenticated activities for local" do
  519. setup do: local_and_remote_activities()
  520. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  521. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  522. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  523. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  524. "error" => "Record not found"
  525. }
  526. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  527. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  528. end
  529. test "if user is authenticated", %{local: local, remote: remote} do
  530. %{conn: conn} = oauth_access(["read"])
  531. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  532. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  533. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  534. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  535. end
  536. end
  537. describe "status with restrict unauthenticated activities for remote" do
  538. setup do: local_and_remote_activities()
  539. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  540. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  541. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  542. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  543. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  544. assert json_response_and_validate_schema(res_conn, :not_found) == %{
  545. "error" => "Record not found"
  546. }
  547. end
  548. test "if user is authenticated", %{local: local, remote: remote} do
  549. %{conn: conn} = oauth_access(["read"])
  550. res_conn = get(conn, "/api/v1/statuses/#{local.id}")
  551. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  552. res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
  553. assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
  554. end
  555. end
  556. test "getting a status that doesn't exist returns 404" do
  557. %{conn: conn} = oauth_access(["read:statuses"])
  558. activity = insert(:note_activity)
  559. conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
  560. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  561. end
  562. test "get a direct status" do
  563. %{user: user, conn: conn} = oauth_access(["read:statuses"])
  564. other_user = insert(:user)
  565. {:ok, activity} =
  566. CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
  567. conn =
  568. conn
  569. |> assign(:user, user)
  570. |> get("/api/v1/statuses/#{activity.id}")
  571. [participation] = Participation.for_user(user)
  572. res = json_response_and_validate_schema(conn, 200)
  573. assert res["pleroma"]["direct_conversation_id"] == participation.id
  574. end
  575. test "get statuses by IDs" do
  576. %{conn: conn} = oauth_access(["read:statuses"])
  577. %{id: id1} = insert(:note_activity)
  578. %{id: id2} = insert(:note_activity)
  579. query_string = "ids[]=#{id1}&ids[]=#{id2}"
  580. conn = get(conn, "/api/v1/statuses/?#{query_string}")
  581. assert [%{"id" => ^id1}, %{"id" => ^id2}] =
  582. Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
  583. end
  584. describe "getting statuses by ids with restricted unauthenticated for local and remote" do
  585. setup do: local_and_remote_activities()
  586. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  587. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  588. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  589. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  590. assert json_response_and_validate_schema(res_conn, 200) == []
  591. end
  592. test "if user is authenticated", %{local: local, remote: remote} do
  593. %{conn: conn} = oauth_access(["read"])
  594. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  595. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  596. end
  597. end
  598. describe "getting statuses by ids with restricted unauthenticated for local" do
  599. setup do: local_and_remote_activities()
  600. setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
  601. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  602. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  603. remote_id = remote.id
  604. assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
  605. end
  606. test "if user is authenticated", %{local: local, remote: remote} do
  607. %{conn: conn} = oauth_access(["read"])
  608. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  609. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  610. end
  611. end
  612. describe "getting statuses by ids with restricted unauthenticated for remote" do
  613. setup do: local_and_remote_activities()
  614. setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
  615. test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
  616. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  617. local_id = local.id
  618. assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
  619. end
  620. test "if user is authenticated", %{local: local, remote: remote} do
  621. %{conn: conn} = oauth_access(["read"])
  622. res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
  623. assert length(json_response_and_validate_schema(res_conn, 200)) == 2
  624. end
  625. end
  626. describe "deleting a status" do
  627. test "when you created it" do
  628. %{user: author, conn: conn} = oauth_access(["write:statuses"])
  629. activity = insert(:note_activity, user: author)
  630. object = Object.normalize(activity)
  631. content = object.data["content"]
  632. source = object.data["source"]
  633. result =
  634. conn
  635. |> assign(:user, author)
  636. |> delete("/api/v1/statuses/#{activity.id}")
  637. |> json_response_and_validate_schema(200)
  638. assert match?(%{"content" => ^content, "text" => ^source}, result)
  639. refute Activity.get_by_id(activity.id)
  640. end
  641. test "when it doesn't exist" do
  642. %{user: author, conn: conn} = oauth_access(["write:statuses"])
  643. activity = insert(:note_activity, user: author)
  644. conn =
  645. conn
  646. |> assign(:user, author)
  647. |> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
  648. assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
  649. end
  650. test "when you didn't create it" do
  651. %{conn: conn} = oauth_access(["write:statuses"])
  652. activity = insert(:note_activity)
  653. conn = delete(conn, "/api/v1/statuses/#{activity.id}")
  654. assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
  655. assert Activity.get_by_id(activity.id) == activity
  656. end
  657. test "when you're an admin or moderator", %{conn: conn} do
  658. activity1 = insert(:note_activity)
  659. activity2 = insert(:note_activity)
  660. admin = insert(:user, is_admin: true)
  661. moderator = insert(:user, is_moderator: true)
  662. res_conn =
  663. conn
  664. |> assign(:user, admin)
  665. |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
  666. |> delete("/api/v1/statuses/#{activity1.id}")
  667. assert %{} = json_response_and_validate_schema(res_conn, 200)
  668. res_conn =
  669. conn
  670. |> assign(:user, moderator)
  671. |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
  672. |> delete("/api/v1/statuses/#{activity2.id}")
  673. assert %{} = json_response_and_validate_schema(res_conn, 200)
  674. refute Activity.get_by_id(activity1.id)
  675. refute Activity.get_by_id(activity2.id)
  676. end
  677. end
  678. describe "reblogging" do
  679. setup do: oauth_access(["write:statuses"])
  680. test "reblogs and returns the reblogged status", %{conn: conn} do
  681. activity = insert(:note_activity)
  682. conn =
  683. conn
  684. |> put_req_header("content-type", "application/json")
  685. |> post("/api/v1/statuses/#{activity.id}/reblog")
  686. assert %{
  687. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
  688. "reblogged" => true
  689. } = json_response_and_validate_schema(conn, 200)
  690. assert to_string(activity.id) == id
  691. end
  692. test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
  693. activity = insert(:note_activity)
  694. conn =
  695. conn
  696. |> put_req_header("content-type", "application/json")
  697. |> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
  698. assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
  699. end
  700. test "reblogs privately and returns the reblogged status", %{conn: conn} do
  701. activity = insert(:note_activity)
  702. conn =
  703. conn
  704. |> put_req_header("content-type", "application/json")
  705. |> post(
  706. "/api/v1/statuses/#{activity.id}/reblog",
  707. %{"visibility" => "private"}
  708. )
  709. assert %{
  710. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
  711. "reblogged" => true,
  712. "visibility" => "private"
  713. } = json_response_and_validate_schema(conn, 200)
  714. assert to_string(activity.id) == id
  715. end
  716. test "reblogged status for another user" do
  717. activity = insert(:note_activity)
  718. user1 = insert(:user)
  719. user2 = insert(:user)
  720. user3 = insert(:user)
  721. {:ok, _} = CommonAPI.favorite(user2, activity.id)
  722. {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
  723. {:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
  724. {:ok, _} = CommonAPI.repeat(activity.id, user2)
  725. conn_res =
  726. build_conn()
  727. |> assign(:user, user3)
  728. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
  729. |> get("/api/v1/statuses/#{reblog_activity1.id}")
  730. assert %{
  731. "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
  732. "reblogged" => false,
  733. "favourited" => false,
  734. "bookmarked" => false
  735. } = json_response_and_validate_schema(conn_res, 200)
  736. conn_res =
  737. build_conn()
  738. |> assign(:user, user2)
  739. |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
  740. |> get("/api/v1/statuses/#{reblog_activity1.id}")
  741. assert %{
  742. "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
  743. "reblogged" => true,
  744. "favourited" => true,
  745. "bookmarked" => true
  746. } = json_response_and_validate_schema(conn_res, 200)
  747. assert to_string(activity.id) == id
  748. end
  749. end
  750. describe "unreblogging" do
  751. setup do: oauth_access(["write:statuses"])
  752. test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
  753. activity = insert(:note_activity)
  754. {:ok, _} = CommonAPI.repeat(activity.id, user)
  755. conn =
  756. conn
  757. |> put_req_header("content-type", "application/json")
  758. |> post("/api/v1/statuses/#{activity.id}/unreblog")
  759. assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
  760. json_response_and_validate_schema(conn, 200)
  761. assert to_string(activity.id) == id
  762. end
  763. test "returns 404 error when activity does not exist", %{conn: conn} do
  764. conn =
  765. conn
  766. |> put_req_header("content-type", "application/json")
  767. |> post("/api/v1/statuses/foo/unreblog")
  768. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  769. end
  770. end
  771. describe "favoriting" do
  772. setup do: oauth_access(["write:favourites"])
  773. test "favs a status and returns it", %{conn: conn} do
  774. activity = insert(:note_activity)
  775. conn =
  776. conn
  777. |> put_req_header("content-type", "application/json")
  778. |> post("/api/v1/statuses/#{activity.id}/favourite")
  779. assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
  780. json_response_and_validate_schema(conn, 200)
  781. assert to_string(activity.id) == id
  782. end
  783. test "favoriting twice will just return 200", %{conn: conn} do
  784. activity = insert(:note_activity)
  785. conn
  786. |> put_req_header("content-type", "application/json")
  787. |> post("/api/v1/statuses/#{activity.id}/favourite")
  788. assert conn
  789. |> put_req_header("content-type", "application/json")
  790. |> post("/api/v1/statuses/#{activity.id}/favourite")
  791. |> json_response_and_validate_schema(200)
  792. end
  793. test "returns 404 error for a wrong id", %{conn: conn} do
  794. conn =
  795. conn
  796. |> put_req_header("content-type", "application/json")
  797. |> post("/api/v1/statuses/1/favourite")
  798. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  799. end
  800. end
  801. describe "unfavoriting" do
  802. setup do: oauth_access(["write:favourites"])
  803. test "unfavorites a status and returns it", %{user: user, conn: conn} do
  804. activity = insert(:note_activity)
  805. {:ok, _} = CommonAPI.favorite(user, activity.id)
  806. conn =
  807. conn
  808. |> put_req_header("content-type", "application/json")
  809. |> post("/api/v1/statuses/#{activity.id}/unfavourite")
  810. assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
  811. json_response_and_validate_schema(conn, 200)
  812. assert to_string(activity.id) == id
  813. end
  814. test "returns 404 error for a wrong id", %{conn: conn} do
  815. conn =
  816. conn
  817. |> put_req_header("content-type", "application/json")
  818. |> post("/api/v1/statuses/1/unfavourite")
  819. assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
  820. end
  821. end
  822. describe "pinned statuses" do
  823. setup do: oauth_access(["write:accounts"])
  824. setup %{user: user} do
  825. {:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
  826. %{activity: activity}
  827. end
  828. setup do: clear_config([:instance, :max_pinned_statuses], 1)
  829. test "pin status", %{conn: conn, user: user, activity: activity} do
  830. id_str = to_string(activity.id)
  831. assert %{"id" => ^id_str, "pinned" => true} =
  832. conn
  833. |> put_req_header("content-type", "application/json")
  834. |> post("/api/v1/statuses/#{activity.id}/pin")
  835. |> json_response_and_validate_schema(200)
  836. assert [%{"id" => ^id_str, "pinned" => true}] =
  837. conn
  838. |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
  839. |> json_response_and_validate_schema(200)
  840. end
  841. test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
  842. {:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
  843. conn =
  844. conn
  845. |> put_req_header("content-type", "application/json")
  846. |> post("/api/v1/statuses/#{dm.id}/pin")
  847. assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not pin"}
  848. end
  849. test "unpin status", %{conn: conn, user: user, activity: activity} do
  850. {:ok, _} = CommonAPI.pin(activity.id, user)
  851. user = refresh_record(user)
  852. id_str = to_string(activity.id)
  853. assert %{"id" => ^id_str, "pinned" => false} =
  854. conn
  855. |> assign(:user, user)
  856. |> post("/api/v1/statuses/#{activity.id}/unpin")
  857. |> json_response_and_validate_schema(200)
  858. assert [] =
  859. conn
  860. |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
  861. |> json_response_and_validate_schema(200)
  862. end
  863. test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do
  864. conn =
  865. conn
  866. |> put_req_header("content-type", "application/json")
  867. |> post("/api/v1/statuses/1/unpin")
  868. assert json_response_and_validate_schema(conn, 400) == %{"error" => "Could not unpin"}
  869. end
  870. test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
  871. {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
  872. id_str_one = to_string(activity_one.id)
  873. assert %{"id" => ^id_str_one, "pinned" => true} =
  874. conn
  875. |> put_req_header("content-type", "application/json")
  876. |> post("/api/v1/statuses/#{id_str_one}/pin")
  877. |> json_response_and_validate_schema(200)
  878. user = refresh_record(user)
  879. assert %{"error" => "You have already pinned the maximum number of statuses"} =
  880. conn
  881. |> assign(:user, user)
  882. |> post("/api/v1/statuses/#{activity_two.id}/pin")
  883. |> json_response_and_validate_schema(400)
  884. end
  885. test "on pin removes deletion job, on unpin reschedule deletion" do
  886. %{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
  887. expires_in = 2 * 60 * 60
  888. expires_at = DateTime.add(DateTime.utc_now(), expires_in)
  889. assert %{"id" => id} =
  890. conn
  891. |> put_req_header("content-type", "application/json")
  892. |> post("api/v1/statuses", %{
  893. "status" => "oolong",
  894. "expires_in" => expires_in
  895. })
  896. |> json_response_and_validate_schema(200)
  897. assert_enqueued(
  898. worker: Pleroma.Workers.PurgeExpiredActivity,
  899. args: %{activity_id: id},
  900. scheduled_at: expires_at
  901. )
  902. assert %{"id" => ^id, "pinned" => true} =
  903. conn
  904. |> put_req_header("content-type", "application/json")
  905. |> post("/api/v1/statuses/#{id}/pin")
  906. |> json_response_and_validate_schema(200)
  907. refute_enqueued(
  908. worker: Pleroma.Workers.PurgeExpiredActivity,
  909. args: %{activity_id: id},
  910. scheduled_at: expires_at
  911. )
  912. assert %{"id" => ^id, "pinned" => false} =
  913. conn
  914. |> put_req_header("content-type", "application/json")
  915. |> post("/api/v1/statuses/#{id}/unpin")
  916. |> json_response_and_validate_schema(200)
  917. assert_enqueued(
  918. worker: Pleroma.Workers.PurgeExpiredActivity,
  919. args: %{activity_id: id},
  920. scheduled_at: expires_at
  921. )
  922. end
  923. end
  924. describe "cards" do
  925. setup do
  926. Config.put([:rich_media, :enabled], true)
  927. oauth_access(["read:statuses"])
  928. end
  929. test "returns rich-media card", %{conn: conn, user: user} do
  930. Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
  931. {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp"})
  932. card_data = %{
  933. "image" => "http://ia.media-imdb.com/images/rock.jpg",
  934. "provider_name" => "example.com",
  935. "provider_url" => "https://example.com",
  936. "title" => "The Rock",
  937. "type" => "link",
  938. "url" => "https://example.com/ogp",
  939. "description" =>
  940. "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
  941. "pleroma" => %{
  942. "opengraph" => %{
  943. "image" => "http://ia.media-imdb.com/images/rock.jpg",
  944. "title" => "The Rock",
  945. "type" => "video.movie",
  946. "url" => "https://example.com/ogp",
  947. "description" =>
  948. "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
  949. }
  950. }
  951. }
  952. response =
  953. conn
  954. |> get("/api/v1/statuses/#{activity.id}/card")
  955. |> json_response_and_validate_schema(200)
  956. assert response == card_data
  957. # works with private posts
  958. {:ok, activity} =
  959. CommonAPI.post(user, %{status: "https://example.com/ogp", visibility: "direct"})
  960. response_two =
  961. conn
  962. |> get("/api/v1/statuses/#{activity.id}/card")
  963. |> json_response_and_validate_schema(200)
  964. assert response_two == card_data
  965. end
  966. test "replaces missing description with an empty string", %{conn: conn, user: user} do
  967. Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
  968. {:ok, activity} = CommonAPI.post(user, %{status: "https://example.com/ogp-missing-data"})
  969. response =
  970. conn
  971. |> get("/api/v1/statuses/#{activity.id}/card")
  972. |> json_response_and_validate_schema(:ok)
  973. assert response == %{
  974. "type" => "link",
  975. "title" => "Pleroma",
  976. "description" => "",
  977. "image" => nil,
  978. "provider_name" => "example.com",
  979. "provider_url" => "https://example.com",
  980. "url" => "https://example.com/ogp-missing-data",
  981. "pleroma" => %{
  982. "opengraph" => %{
  983. "title" => "Pleroma",
  984. "type" => "website",
  985. "url" => "https://example.com/ogp-missing-data"
  986. }
  987. }
  988. }
  989. end
  990. end
  991. test "bookmarks" do
  992. bookmarks_uri = "/api/v1/bookmarks"
  993. %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
  994. author = insert(:user)
  995. {:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
  996. {:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
  997. response1 =
  998. conn
  999. |> put_req_header("content-type", "application/json")
  1000. |> post("/api/v1/statuses/#{activity1.id}/bookmark")
  1001. assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
  1002. response2 =
  1003. conn
  1004. |> put_req_header("content-type", "application/json")
  1005. |> post("/api/v1/statuses/#{activity2.id}/bookmark")
  1006. assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
  1007. bookmarks = get(conn, bookmarks_uri)
  1008. assert [
  1009. json_response_and_validate_schema(response2, 200),
  1010. json_response_and_validate_schema(response1, 200)
  1011. ] ==
  1012. json_response_and_validate_schema(bookmarks, 200)
  1013. response1 =
  1014. conn
  1015. |> put_req_header("content-type", "application/json")
  1016. |> post("/api/v1/statuses/#{activity1.id}/unbookmark")
  1017. assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
  1018. bookmarks = get(conn, bookmarks_uri)
  1019. assert [json_response_and_validate_schema(response2, 200)] ==
  1020. json_response_and_validate_schema(bookmarks, 200)
  1021. end
  1022. describe "conversation muting" do
  1023. setup do: oauth_access(["write:mutes"])
  1024. setup do
  1025. post_user = insert(:user)
  1026. {:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
  1027. %{activity: activity}
  1028. end
  1029. test "mute conversation", %{conn: conn, activity: activity} do
  1030. id_str = to_string(activity.id)
  1031. assert %{"id" => ^id_str, "muted" => true} =
  1032. conn
  1033. |> put_req_header("content-type", "application/json")
  1034. |> post("/api/v1/statuses/#{activity.id}/mute")
  1035. |> json_response_and_validate_schema(200)
  1036. end
  1037. test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
  1038. {:ok, _} = CommonAPI.add_mute(user, activity)
  1039. conn =
  1040. conn
  1041. |> put_req_header("content-type", "application/json")
  1042. |> post("/api/v1/statuses/#{activity.id}/mute")
  1043. assert json_response_and_validate_schema(conn, 400) == %{
  1044. "error" => "conversation is already muted"
  1045. }
  1046. end
  1047. test "unmute conversation", %{conn: conn, user: user, activity: activity} do
  1048. {:ok, _} = CommonAPI.add_mute(user, activity)
  1049. id_str = to_string(activity.id)
  1050. assert %{"id" => ^id_str, "muted" => false} =
  1051. conn
  1052. # |> assign(:user, user)
  1053. |> post("/api/v1/statuses/#{activity.id}/unmute")
  1054. |> json_response_and_validate_schema(200)
  1055. end
  1056. end
  1057. test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
  1058. user1 = insert(:user)
  1059. user2 = insert(:user)
  1060. user3 = insert(:user)
  1061. {:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
  1062. # Reply to status from another user
  1063. conn1 =
  1064. conn
  1065. |> assign(:user, user2)
  1066. |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
  1067. |> put_req_header("content-type", "application/json")
  1068. |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
  1069. assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
  1070. activity = Activity.get_by_id_with_object(id)
  1071. assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
  1072. assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
  1073. # Reblog from the third user
  1074. conn2 =
  1075. conn
  1076. |> assign(:user, user3)
  1077. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
  1078. |> put_req_header("content-type", "application/json")
  1079. |> post("/api/v1/statuses/#{activity.id}/reblog")
  1080. assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
  1081. json_response_and_validate_schema(conn2, 200)
  1082. assert to_string(activity.id) == id
  1083. # Getting third user status
  1084. conn3 =
  1085. conn
  1086. |> assign(:user, user3)
  1087. |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
  1088. |> get("api/v1/timelines/home")
  1089. [reblogged_activity] = json_response(conn3, 200)
  1090. assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
  1091. replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
  1092. assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
  1093. end
  1094. describe "GET /api/v1/statuses/:id/favourited_by" do
  1095. setup do: oauth_access(["read:accounts"])
  1096. setup %{user: user} do
  1097. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1098. %{activity: activity}
  1099. end
  1100. test "returns users who have favorited the status", %{conn: conn, activity: activity} do
  1101. other_user = insert(:user)
  1102. {:ok, _} = CommonAPI.favorite(other_user, activity.id)
  1103. response =
  1104. conn
  1105. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1106. |> json_response_and_validate_schema(:ok)
  1107. [%{"id" => id}] = response
  1108. assert id == other_user.id
  1109. end
  1110. test "returns empty array when status has not been favorited yet", %{
  1111. conn: conn,
  1112. activity: activity
  1113. } do
  1114. response =
  1115. conn
  1116. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1117. |> json_response_and_validate_schema(:ok)
  1118. assert Enum.empty?(response)
  1119. end
  1120. test "does not return users who have favorited the status but are blocked", %{
  1121. conn: %{assigns: %{user: user}} = conn,
  1122. activity: activity
  1123. } do
  1124. other_user = insert(:user)
  1125. {:ok, _user_relationship} = User.block(user, other_user)
  1126. {:ok, _} = CommonAPI.favorite(other_user, activity.id)
  1127. response =
  1128. conn
  1129. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1130. |> json_response_and_validate_schema(:ok)
  1131. assert Enum.empty?(response)
  1132. end
  1133. test "does not fail on an unauthenticated request", %{activity: activity} do
  1134. other_user = insert(:user)
  1135. {:ok, _} = CommonAPI.favorite(other_user, activity.id)
  1136. response =
  1137. build_conn()
  1138. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1139. |> json_response_and_validate_schema(:ok)
  1140. [%{"id" => id}] = response
  1141. assert id == other_user.id
  1142. end
  1143. test "requires authentication for private posts", %{user: user} do
  1144. other_user = insert(:user)
  1145. {:ok, activity} =
  1146. CommonAPI.post(user, %{
  1147. status: "@#{other_user.nickname} wanna get some #cofe together?",
  1148. visibility: "direct"
  1149. })
  1150. {:ok, _} = CommonAPI.favorite(other_user, activity.id)
  1151. favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
  1152. build_conn()
  1153. |> get(favourited_by_url)
  1154. |> json_response_and_validate_schema(404)
  1155. conn =
  1156. build_conn()
  1157. |> assign(:user, other_user)
  1158. |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
  1159. conn
  1160. |> assign(:token, nil)
  1161. |> get(favourited_by_url)
  1162. |> json_response_and_validate_schema(404)
  1163. response =
  1164. conn
  1165. |> get(favourited_by_url)
  1166. |> json_response_and_validate_schema(200)
  1167. [%{"id" => id}] = response
  1168. assert id == other_user.id
  1169. end
  1170. test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
  1171. clear_config([:instance, :show_reactions], false)
  1172. other_user = insert(:user)
  1173. {:ok, _} = CommonAPI.favorite(other_user, activity.id)
  1174. response =
  1175. conn
  1176. |> get("/api/v1/statuses/#{activity.id}/favourited_by")
  1177. |> json_response_and_validate_schema(:ok)
  1178. assert Enum.empty?(response)
  1179. end
  1180. end
  1181. describe "GET /api/v1/statuses/:id/reblogged_by" do
  1182. setup do: oauth_access(["read:accounts"])
  1183. setup %{user: user} do
  1184. {:ok, activity} = CommonAPI.post(user, %{status: "test"})
  1185. %{activity: activity}
  1186. end
  1187. test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
  1188. other_user = insert(:user)
  1189. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1190. response =
  1191. conn
  1192. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1193. |> json_response_and_validate_schema(:ok)
  1194. [%{"id" => id}] = response
  1195. assert id == other_user.id
  1196. end
  1197. test "returns empty array when status has not been reblogged yet", %{
  1198. conn: conn,
  1199. activity: activity
  1200. } do
  1201. response =
  1202. conn
  1203. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1204. |> json_response_and_validate_schema(:ok)
  1205. assert Enum.empty?(response)
  1206. end
  1207. test "does not return users who have reblogged the status but are blocked", %{
  1208. conn: %{assigns: %{user: user}} = conn,
  1209. activity: activity
  1210. } do
  1211. other_user = insert(:user)
  1212. {:ok, _user_relationship} = User.block(user, other_user)
  1213. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1214. response =
  1215. conn
  1216. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1217. |> json_response_and_validate_schema(:ok)
  1218. assert Enum.empty?(response)
  1219. end
  1220. test "does not return users who have reblogged the status privately", %{
  1221. conn: conn
  1222. } do
  1223. other_user = insert(:user)
  1224. {:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
  1225. {:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
  1226. response =
  1227. conn
  1228. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1229. |> json_response_and_validate_schema(:ok)
  1230. assert Enum.empty?(response)
  1231. end
  1232. test "does not fail on an unauthenticated request", %{activity: activity} do
  1233. other_user = insert(:user)
  1234. {:ok, _} = CommonAPI.repeat(activity.id, other_user)
  1235. response =
  1236. build_conn()
  1237. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1238. |> json_response_and_validate_schema(:ok)
  1239. [%{"id" => id}] = response
  1240. assert id == other_user.id
  1241. end
  1242. test "requires authentication for private posts", %{user: user} do
  1243. other_user = insert(:user)
  1244. {:ok, activity} =
  1245. CommonAPI.post(user, %{
  1246. status: "@#{other_user.nickname} wanna get some #cofe together?",
  1247. visibility: "direct"
  1248. })
  1249. build_conn()
  1250. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1251. |> json_response_and_validate_schema(404)
  1252. response =
  1253. build_conn()
  1254. |> assign(:user, other_user)
  1255. |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
  1256. |> get("/api/v1/statuses/#{activity.id}/reblogged_by")
  1257. |> json_response_and_validate_schema(200)
  1258. assert [] == response
  1259. end
  1260. end
  1261. test "context" do
  1262. user = insert(:user)
  1263. {:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
  1264. {:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
  1265. {:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
  1266. {:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
  1267. {:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
  1268. response =
  1269. build_conn()
  1270. |> get("/api/v1/statuses/#{id3}/context")
  1271. |> json_response_and_validate_schema(:ok)
  1272. assert %{
  1273. "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
  1274. "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
  1275. } = response
  1276. end
  1277. test "favorites paginate correctly" do
  1278. %{user: user, conn: conn} = oauth_access(["read:favourites"])
  1279. other_user = insert(:user)
  1280. {:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
  1281. {:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
  1282. {:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
  1283. {:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
  1284. {:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
  1285. {:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
  1286. result =
  1287. conn
  1288. |> get("/api/v1/favourites?limit=1")
  1289. assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
  1290. assert post_id == second_post.id
  1291. # Using the header for pagination works correctly
  1292. [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
  1293. [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
  1294. assert max_id == third_favorite.id
  1295. result =
  1296. conn
  1297. |> get("/api/v1/favourites?max_id=#{max_id}")
  1298. assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
  1299. json_response_and_validate_schema(result, 200)
  1300. assert first_post_id == first_post.id
  1301. assert third_post_id == third_post.id
  1302. end
  1303. test "returns the favorites of a user" do
  1304. %{user: user, conn: conn} = oauth_access(["read:favourites"])
  1305. other_user = insert(:user)
  1306. {:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
  1307. {:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
  1308. {:ok, last_like} = CommonAPI.favorite(user, activity.id)
  1309. first_conn = get(conn, "/api/v1/favourites")
  1310. assert [status] = json_response_and_validate_schema(first_conn, 200)
  1311. assert status["id"] == to_string(activity.id)
  1312. assert [{"link", _link_header}] =
  1313. Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
  1314. # Honours query params
  1315. {:ok, second_activity} =
  1316. CommonAPI.post(other_user, %{
  1317. status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
  1318. })
  1319. {:ok, _} = CommonAPI.favorite(user, second_activity.id)
  1320. second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
  1321. assert [second_status] = json_response_and_validate_schema(second_conn, 200)
  1322. assert second_status["id"] == to_string(second_activity.id)
  1323. third_conn = get(conn, "/api/v1/favourites?limit=0")
  1324. assert [] = json_response_and_validate_schema(third_conn, 200)
  1325. end
  1326. test "expires_at is nil for another user" do
  1327. %{conn: conn, user: user} = oauth_access(["read:statuses"])
  1328. expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
  1329. {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
  1330. assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
  1331. conn
  1332. |> get("/api/v1/statuses/#{activity.id}")
  1333. |> json_response_and_validate_schema(:ok)
  1334. {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
  1335. assert DateTime.diff(expires_at, a_expires_at) == 0
  1336. %{conn: conn} = oauth_access(["read:statuses"])
  1337. assert %{"pleroma" => %{"expires_at" => nil}} =
  1338. conn
  1339. |> get("/api/v1/statuses/#{activity.id}")
  1340. |> json_response_and_validate_schema(:ok)
  1341. end
  1342. end