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.PleromaAPI.ChatControllerTest do
5 use Pleroma.Web.ConnCase
8 alias Pleroma.Chat.MessageReference
10 alias Pleroma.Tests.Helpers
11 alias Pleroma.UnstubbedConfigMock, as: ConfigMock
13 alias Pleroma.Web.ActivityPub.ActivityPub
14 alias Pleroma.Web.CommonAPI
17 import Pleroma.Factory
19 describe "POST /api/v1/pleroma/chats/:id/messages/:message_id/read" do
20 setup do: oauth_access(["write:chats"])
22 test "it marks one message as read", %{conn: conn, user: user} do
23 other_user = insert(:user)
25 {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
26 {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
27 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
28 object = Object.normalize(create, fetch: false)
29 cm_ref = MessageReference.for_chat_and_object(chat, object)
31 assert cm_ref.unread == true
35 |> post("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}/read")
36 |> json_response_and_validate_schema(200)
38 assert result["unread"] == false
40 cm_ref = MessageReference.for_chat_and_object(chat, object)
42 assert cm_ref.unread == false
46 describe "POST /api/v1/pleroma/chats/:id/read" do
47 setup do: oauth_access(["write:chats"])
49 test "given a `last_read_id`, it marks everything until then as read", %{
53 other_user = insert(:user)
55 {:ok, create} = CommonAPI.post_chat_message(other_user, user, "sup")
56 {:ok, _create} = CommonAPI.post_chat_message(other_user, user, "sup part 2")
57 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
58 object = Object.normalize(create, fetch: false)
59 cm_ref = MessageReference.for_chat_and_object(chat, object)
61 assert cm_ref.unread == true
65 |> put_req_header("content-type", "application/json")
66 |> post("/api/v1/pleroma/chats/#{chat.id}/read", %{"last_read_id" => cm_ref.id})
67 |> json_response_and_validate_schema(200)
69 assert result["unread"] == 1
71 cm_ref = MessageReference.for_chat_and_object(chat, object)
73 assert cm_ref.unread == false
77 describe "POST /api/v1/pleroma/chats/:id/messages" do
78 setup do: oauth_access(["write:chats"])
80 test "it posts a message to the chat", %{conn: conn, user: user} do
81 other_user = insert(:user)
83 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
87 |> put_req_header("content-type", "application/json")
88 |> put_req_header("idempotency-key", "123")
89 |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "Hallo!!"})
90 |> json_response_and_validate_schema(200)
92 assert result["content"] == "Hallo!!"
93 assert result["chat_id"] == chat.id |> to_string()
94 assert result["idempotency_key"] == "123"
97 test "it fails if there is no content", %{conn: conn, user: user} do
98 other_user = insert(:user)
100 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
104 |> put_req_header("content-type", "application/json")
105 |> post("/api/v1/pleroma/chats/#{chat.id}/messages")
106 |> json_response_and_validate_schema(400)
108 assert %{"error" => "no_content"} == result
111 test "it works with an attachment", %{conn: conn, user: user} do
113 content_type: "image/jpeg",
114 path: Path.absname("test/fixtures/image.jpg"),
115 filename: "an_image.jpg"
119 |> stub_with(Pleroma.Test.StaticConfig)
121 {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
123 other_user = insert(:user)
125 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
129 |> put_req_header("content-type", "application/json")
130 |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{
131 "media_id" => to_string(upload.id)
133 |> json_response_and_validate_schema(200)
135 assert result["attachment"]
138 test "gets MRF reason when rejected", %{conn: conn, user: user} do
139 clear_config([:mrf_keyword, :reject], ["GNO"])
140 clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
142 other_user = insert(:user)
144 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
148 |> put_req_header("content-type", "application/json")
149 |> post("/api/v1/pleroma/chats/#{chat.id}/messages", %{"content" => "GNO/Linux"})
150 |> json_response_and_validate_schema(422)
152 assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} == result
156 describe "DELETE /api/v1/pleroma/chats/:id/messages/:message_id" do
157 setup do: oauth_access(["write:chats"])
159 test "it deletes a message from the chat", %{conn: conn, user: user} do
160 recipient = insert(:user)
163 CommonAPI.post_chat_message(user, recipient, "Hello darkness my old friend")
165 {:ok, other_message} = CommonAPI.post_chat_message(recipient, user, "nico nico ni")
167 object = Object.normalize(message, fetch: false)
169 chat = Chat.get(user.id, recipient.ap_id)
171 cm_ref = MessageReference.for_chat_and_object(chat, object)
173 # Deleting your own message removes the message and the reference
176 |> put_req_header("content-type", "application/json")
177 |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
178 |> json_response_and_validate_schema(200)
180 assert result["id"] == cm_ref.id
181 refute MessageReference.get_by_id(cm_ref.id)
182 assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id)
184 # Deleting other people's messages just removes the reference
185 object = Object.normalize(other_message, fetch: false)
186 cm_ref = MessageReference.for_chat_and_object(chat, object)
190 |> put_req_header("content-type", "application/json")
191 |> delete("/api/v1/pleroma/chats/#{chat.id}/messages/#{cm_ref.id}")
192 |> json_response_and_validate_schema(200)
194 assert result["id"] == cm_ref.id
195 refute MessageReference.get_by_id(cm_ref.id)
196 assert Object.get_by_id(object.id)
200 describe "GET /api/v1/pleroma/chats/:id/messages" do
201 setup do: oauth_access(["read:chats"])
203 test "it paginates", %{conn: conn, user: user} do
204 recipient = insert(:user)
206 Enum.each(1..30, fn _ ->
207 {:ok, _} = CommonAPI.post_chat_message(user, recipient, "hey")
210 chat = Chat.get(user.id, recipient.ap_id)
212 response = get(conn, "/api/v1/pleroma/chats/#{chat.id}/messages")
213 result = json_response_and_validate_schema(response, 200)
215 [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
216 api_endpoint = Pleroma.Web.Endpoint.url() <> "/api/v1/pleroma/chats/"
218 [next_url, next_rel] = String.split(next, ";")
219 next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<")
221 next_url_sorted = Helpers.uri_query_sort(next_url)
223 assert String.match?(
225 ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&offset=\d+$)
228 assert next_rel =~ "next"
230 [prev_url, prev_rel] = String.split(prev, ";")
231 prev_url = String.trim_trailing(prev_url, ">") |> String.trim_leading("<")
233 prev_url_sorted = Helpers.uri_query_sort(prev_url)
235 assert String.match?(
237 ~r(#{api_endpoint}.*/messages\?limit=\d+&min_id=.*&offset=\d+$)
240 assert prev_rel =~ "prev"
242 assert length(result) == 20
244 response = get(conn, "#{api_endpoint}#{chat.id}/messages?max_id=#{List.last(result)["id"]}")
246 result = json_response_and_validate_schema(response, 200)
247 [next, prev] = get_resp_header(response, "link") |> hd() |> String.split(", ")
249 [next_url, next_rel] = String.split(next, ";")
250 next_url = String.trim_trailing(next_url, ">") |> String.trim_leading("<")
252 next_url_sorted = Helpers.uri_query_sort(next_url)
254 assert String.match?(
256 ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&offset=\d+$)
259 assert next_rel =~ "next"
261 [prev_url, prev_rel] = String.split(prev, ";")
262 prev_url = String.trim_trailing(prev_url, ">") |> String.trim_leading("<")
264 prev_url_sorted = Helpers.uri_query_sort(prev_url)
266 assert String.match?(
268 ~r(#{api_endpoint}.*/messages\?limit=\d+&max_id=.*&min_id=.*&offset=\d+$)
271 assert prev_rel =~ "prev"
273 assert length(result) == 10
276 test "it returns the messages for a given chat", %{conn: conn, user: user} do
277 other_user = insert(:user)
278 third_user = insert(:user)
280 {:ok, _} = CommonAPI.post_chat_message(user, other_user, "hey")
281 {:ok, _} = CommonAPI.post_chat_message(user, third_user, "hey")
282 {:ok, _} = CommonAPI.post_chat_message(user, other_user, "how are you?")
283 {:ok, _} = CommonAPI.post_chat_message(other_user, user, "fine, how about you?")
285 chat = Chat.get(user.id, other_user.ap_id)
289 |> get("/api/v1/pleroma/chats/#{chat.id}/messages")
290 |> json_response_and_validate_schema(200)
293 |> Enum.each(fn message ->
294 assert message["chat_id"] == chat.id |> to_string()
297 assert length(result) == 3
299 # Trying to get the chat of a different user
300 other_user_chat = Chat.get(other_user.id, user.ap_id)
303 |> get("/api/v1/pleroma/chats/#{other_user_chat.id}/messages")
304 |> json_response_and_validate_schema(404)
308 describe "POST /api/v1/pleroma/chats/by-account-id/:id" do
309 setup do: oauth_access(["write:chats"])
311 test "it creates or returns a chat", %{conn: conn} do
312 other_user = insert(:user)
316 |> post("/api/v1/pleroma/chats/by-account-id/#{other_user.id}")
317 |> json_response_and_validate_schema(200)
323 describe "GET /api/v1/pleroma/chats/:id" do
324 setup do: oauth_access(["read:chats"])
326 test "it returns a chat", %{conn: conn, user: user} do
327 other_user = insert(:user)
329 {:ok, chat} = Chat.get_or_create(user.id, other_user.ap_id)
333 |> get("/api/v1/pleroma/chats/#{chat.id}")
334 |> json_response_and_validate_schema(200)
336 assert result["id"] == to_string(chat.id)
340 for tested_endpoint <- ["/api/v1/pleroma/chats", "/api/v2/pleroma/chats"] do
341 describe "GET #{tested_endpoint}" do
342 setup do: oauth_access(["read:chats"])
344 test "it does not return chats with deleted users", %{conn: conn, user: user} do
345 recipient = insert(:user)
346 {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
348 Pleroma.Repo.delete(recipient)
349 User.invalidate_cache(recipient)
353 |> get(unquote(tested_endpoint))
354 |> json_response_and_validate_schema(200)
356 assert length(result) == 0
359 test "it does not return chats with users you blocked", %{conn: conn, user: user} do
360 recipient = insert(:user)
362 {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
366 |> get(unquote(tested_endpoint))
367 |> json_response_and_validate_schema(200)
369 assert length(result) == 1
371 User.block(user, recipient)
375 |> get(unquote(tested_endpoint))
376 |> json_response_and_validate_schema(200)
378 assert length(result) == 0
381 test "it does not return chats with users you muted", %{conn: conn, user: user} do
382 recipient = insert(:user)
384 {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
388 |> get(unquote(tested_endpoint))
389 |> json_response_and_validate_schema(200)
391 assert length(result) == 1
393 User.mute(user, recipient)
397 |> get(unquote(tested_endpoint))
398 |> json_response_and_validate_schema(200)
400 assert length(result) == 0
404 |> get("#{unquote(tested_endpoint)}?with_muted=true")
405 |> json_response_and_validate_schema(200)
407 assert length(result) == 1
410 if tested_endpoint == "/api/v1/pleroma/chats" do
411 test "it returns all chats", %{conn: conn, user: user} do
412 Enum.each(1..30, fn _ ->
413 recipient = insert(:user)
414 {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
419 |> get(unquote(tested_endpoint))
420 |> json_response_and_validate_schema(200)
422 assert length(result) == 30
425 test "it paginates chats", %{conn: conn, user: user} do
426 Enum.each(1..30, fn _ ->
427 recipient = insert(:user)
428 {:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
433 |> get(unquote(tested_endpoint))
434 |> json_response_and_validate_schema(200)
436 assert length(result) == 20
437 last_id = List.last(result)["id"]
441 |> get(unquote(tested_endpoint) <> "?max_id=#{last_id}")
442 |> json_response_and_validate_schema(200)
444 assert length(result) == 10
448 test "it return a list of chats the current user is participating in, in descending order of updates",
449 %{conn: conn, user: user} do
451 jafnhar = insert(:user)
452 tridi = insert(:user)
454 {:ok, chat_1} = Chat.get_or_create(user.id, har.ap_id)
455 {:ok, chat_1} = time_travel(chat_1, -3)
456 {:ok, chat_2} = Chat.get_or_create(user.id, jafnhar.ap_id)
457 {:ok, _chat_2} = time_travel(chat_2, -2)
458 {:ok, chat_3} = Chat.get_or_create(user.id, tridi.ap_id)
459 {:ok, chat_3} = time_travel(chat_3, -1)
461 # bump the second one
462 {:ok, chat_2} = Chat.bump_or_create(user.id, jafnhar.ap_id)
466 |> get(unquote(tested_endpoint))
467 |> json_response_and_validate_schema(200)
469 ids = Enum.map(result, & &1["id"])
472 chat_2.id |> to_string(),
473 chat_3.id |> to_string(),
474 chat_1.id |> to_string()
478 test "it is not affected by :restrict_unauthenticated setting (issue #1973)", %{
482 clear_config([:restrict_unauthenticated, :profiles, :local], true)
483 clear_config([:restrict_unauthenticated, :profiles, :remote], true)
485 user2 = insert(:user)
486 user3 = insert(:user, local: false)
488 {:ok, _chat_12} = Chat.get_or_create(user.id, user2.ap_id)
489 {:ok, _chat_13} = Chat.get_or_create(user.id, user3.ap_id)
493 |> get(unquote(tested_endpoint))
494 |> json_response_and_validate_schema(200)
496 account_ids = Enum.map(result, &get_in(&1, ["account", "id"]))
497 assert Enum.sort(account_ids) == Enum.sort([user2.id, user3.id])