forked from AkkomaGang/akkoma
FloatingGhost
98cb255d12
OTP builds to 1.15 Changelog entry Ensure policies are fully loaded Fix :warn use main branch for linkify Fix warn in tests Migrations for phoenix 1.17 Revert "Migrations for phoenix 1.17" This reverts commit 6a3b2f15b74ea5e33150529385215b7a531f3999. Oban upgrade Add default empty whitelist mix format limit test to amd64 OTP 26 tests for 1.15 use OTP_VERSION tag baka just 1.15 Massive deps update Update locale, deps Mix format shell???? multiline??? ? max cases 1 use assert_recieve don't put_env in async tests don't async conn/fs tests mix format FIx some uploader issues Fix tests
2457 lines
79 KiB
Elixir
2457 lines
79 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
|
|
use Pleroma.Web.ConnCase, async: false
|
|
use Oban.Testing, repo: Pleroma.Repo
|
|
|
|
alias Pleroma.Activity
|
|
alias Pleroma.Conversation.Participation
|
|
alias Pleroma.Object
|
|
alias Pleroma.Repo
|
|
alias Pleroma.ScheduledActivity
|
|
alias Pleroma.Tests.ObanHelpers
|
|
alias Pleroma.User
|
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
alias Pleroma.Web.ActivityPub.Utils
|
|
alias Pleroma.Web.CommonAPI
|
|
alias Pleroma.Workers.ScheduledActivityWorker
|
|
|
|
import Pleroma.Factory
|
|
|
|
setup do: clear_config([:instance, :federating])
|
|
setup do: clear_config([:instance, :allow_relay])
|
|
setup do: clear_config([:rich_media, :enabled])
|
|
setup do: clear_config([:mrf, :policies])
|
|
setup do: clear_config([:mrf_keyword, :reject])
|
|
setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
|
|
setup do: clear_config([Pleroma.Uploaders.Local, :uploads], "uploads")
|
|
|
|
describe "posting statuses" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
|
|
clear_config([:instance, :federating], true)
|
|
Config.get([:instance, :allow_relay], true)
|
|
|
|
response =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"content_type" => "text/plain",
|
|
"source" => "Pleroma FE",
|
|
"status" => "Hello world",
|
|
"visibility" => "public"
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert response["reblogs_count"] == 0
|
|
ObanHelpers.perform_all()
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{response["id"]}", %{})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert response["reblogs_count"] == 0
|
|
end
|
|
|
|
test "posting a status", %{conn: conn} do
|
|
idempotency_key = "Pikachu rocks!"
|
|
|
|
conn_one =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("idempotency-key", idempotency_key)
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe",
|
|
"spoiler_text" => "2hu",
|
|
"sensitive" => "0",
|
|
"language" => "ja"
|
|
})
|
|
|
|
assert %{
|
|
"content" => "cofe",
|
|
"id" => id,
|
|
"spoiler_text" => "2hu",
|
|
"sensitive" => false,
|
|
"language" => "ja"
|
|
} = json_response_and_validate_schema(conn_one, 200)
|
|
|
|
assert Activity.get_by_id(id)
|
|
|
|
conn_two =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put_req_header("idempotency-key", idempotency_key)
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe",
|
|
"spoiler_text" => "2hu",
|
|
"sensitive" => 0
|
|
})
|
|
|
|
# Idempotency plug response means detection fail
|
|
assert %{"id" => second_id} = json_response(conn_two, 200)
|
|
assert id == second_id
|
|
|
|
conn_three =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe",
|
|
"spoiler_text" => "2hu",
|
|
"sensitive" => "False"
|
|
})
|
|
|
|
assert %{"id" => third_id} = json_response_and_validate_schema(conn_three, 200)
|
|
refute id == third_id
|
|
|
|
# An activity that will expire:
|
|
# 2 hours
|
|
expires_in = 2 * 60 * 60
|
|
|
|
expires_at = DateTime.add(DateTime.utc_now(), expires_in)
|
|
|
|
conn_four =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "oolong",
|
|
"expires_in" => expires_in
|
|
})
|
|
|
|
assert %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
|
|
|
|
assert Activity.get_by_id(fourth_id)
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: fourth_id},
|
|
scheduled_at: expires_at
|
|
)
|
|
end
|
|
|
|
test "automatically setting a post expiry if status_ttl_days is set" do
|
|
user = insert(:user, status_ttl_days: 1)
|
|
%{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "aa chikichiki banban"
|
|
})
|
|
|
|
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
|
|
|
|
activity = Activity.get_by_id_with_object(id)
|
|
{:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
|
|
|
assert Timex.diff(
|
|
expires_at,
|
|
DateTime.utc_now(),
|
|
:hours
|
|
) == 23
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: DateTime.add(DateTime.utc_now(), 1 * 60 * 60 * 24)
|
|
)
|
|
end
|
|
|
|
test "it fails to create a status if `expires_in` is less or equal than an hour", %{
|
|
conn: conn
|
|
} do
|
|
# 1 minute
|
|
expires_in = 1 * 60
|
|
|
|
assert %{"error" => "Expiry date is too soon"} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "oolong",
|
|
"expires_in" => expires_in
|
|
})
|
|
|> json_response_and_validate_schema(422)
|
|
|
|
# 5 minutes
|
|
expires_in = 5 * 60
|
|
|
|
assert %{"error" => "Expiry date is too soon"} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "oolong",
|
|
"expires_in" => expires_in
|
|
})
|
|
|> json_response_and_validate_schema(422)
|
|
end
|
|
|
|
test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
|
|
clear_config([:mrf_keyword, :reject], ["GNO"])
|
|
clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
|
|
|
|
assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "GNO/Linux"})
|
|
|> json_response_and_validate_schema(422)
|
|
end
|
|
|
|
test "posting an undefined status with an attachment", %{user: user, conn: conn} do
|
|
file = %Plug.Upload{
|
|
content_type: "image/jpeg",
|
|
path: Path.absname("test/fixtures/image.jpg"),
|
|
filename: "an_image.jpg"
|
|
}
|
|
|
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"media_ids" => [to_string(upload.id)]
|
|
})
|
|
|
|
assert json_response_and_validate_schema(conn, 200)
|
|
end
|
|
|
|
test "posting a status with an invalid language", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe",
|
|
"language" => "invalid"
|
|
})
|
|
|
|
assert %{"error" => "Invalid language"} = json_response_and_validate_schema(conn, 422)
|
|
end
|
|
|
|
test "replying to a status", %{user: user, conn: conn} do
|
|
{:ok, replied_to} = CommonAPI.post(user, %{status: "cofe"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
|
|
|
|
assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
|
|
|
|
activity = Activity.get_by_id(id)
|
|
|
|
assert activity.data["context"] == replied_to.data["context"]
|
|
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
|
|
end
|
|
|
|
test "replying to a direct message with visibility other than direct", %{
|
|
user: user,
|
|
conn: conn
|
|
} do
|
|
{:ok, replied_to} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
|
|
|
|
Enum.each(["public", "private", "unlisted"], fn visibility ->
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "@#{user.nickname} hey",
|
|
"in_reply_to_id" => replied_to.id,
|
|
"visibility" => visibility
|
|
})
|
|
|
|
assert json_response_and_validate_schema(conn, 422) == %{
|
|
"error" => "The message visibility must be direct"
|
|
}
|
|
end)
|
|
end
|
|
|
|
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
|
|
|
|
assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn, 200)
|
|
assert Activity.get_by_id(id)
|
|
end
|
|
|
|
test "posting a sensitive status", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
|
|
|
|
assert %{"content" => "cofe", "id" => id, "sensitive" => true} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert Activity.get_by_id(id)
|
|
end
|
|
|
|
test "posting a fake status", %{conn: conn} do
|
|
real_conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" =>
|
|
"\"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"
|
|
})
|
|
|
|
real_status = json_response_and_validate_schema(real_conn, 200)
|
|
|
|
assert real_status
|
|
assert Object.get_by_ap_id(real_status["uri"])
|
|
|
|
real_status =
|
|
real_status
|
|
|> Map.put("id", nil)
|
|
|> Map.put("url", nil)
|
|
|> Map.put("uri", nil)
|
|
|> Map.put("created_at", nil)
|
|
|> Kernel.put_in(["pleroma", "context"], nil)
|
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
|
|
|
fake_conn =
|
|
conn
|
|
|> assign(:user, refresh_record(conn.assigns.user))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" =>
|
|
"\"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",
|
|
"preview" => true
|
|
})
|
|
|
|
fake_status = json_response_and_validate_schema(fake_conn, 200)
|
|
|
|
assert fake_status
|
|
refute Object.get_by_ap_id(fake_status["uri"])
|
|
|
|
fake_status =
|
|
fake_status
|
|
|> Map.put("id", nil)
|
|
|> Map.put("url", nil)
|
|
|> Map.put("uri", nil)
|
|
|> Map.put("created_at", nil)
|
|
|> Kernel.put_in(["pleroma", "context"], nil)
|
|
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
|
|
|
|
assert real_status == fake_status
|
|
end
|
|
|
|
test "fake statuses' preview card is not cached", %{conn: conn} do
|
|
clear_config([:rich_media, :enabled], true)
|
|
|
|
Tesla.Mock.mock_global(fn
|
|
%{
|
|
method: :get,
|
|
url: "https://example.com/twitter-card"
|
|
} ->
|
|
%Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}
|
|
|
|
env ->
|
|
apply(HttpRequestMock, :request, [env])
|
|
end)
|
|
|
|
conn1 =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "https://example.com/ogp",
|
|
"preview" => true
|
|
})
|
|
|
|
conn2 =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "https://example.com/twitter-card",
|
|
"preview" => true
|
|
})
|
|
|
|
assert %{"card" => %{"title" => "The Rock"}} = json_response_and_validate_schema(conn1, 200)
|
|
|
|
assert %{"card" => %{"title" => "Small Island Developing States Photo Submission"}} =
|
|
json_response_and_validate_schema(conn2, 200)
|
|
end
|
|
|
|
test "posting a status with OGP link preview", %{conn: conn} do
|
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
|
clear_config([:rich_media, :enabled], true)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "https://example.com/ogp"
|
|
})
|
|
|
|
assert %{"id" => id, "card" => %{"title" => "The Rock"}} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert Activity.get_by_id(id)
|
|
end
|
|
|
|
test "posting a direct status", %{conn: conn} do
|
|
user2 = insert(:user)
|
|
content = "direct cofe @#{user2.nickname}"
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => content, "visibility" => "direct"})
|
|
|
|
assert %{"id" => id} = response = json_response_and_validate_schema(conn, 200)
|
|
assert response["visibility"] == "direct"
|
|
assert response["pleroma"]["direct_conversation_id"]
|
|
assert activity = Activity.get_by_id(id)
|
|
assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
|
|
assert activity.data["to"] == [user2.ap_id]
|
|
assert activity.data["cc"] == []
|
|
end
|
|
|
|
test "discloses application metadata when enabled" do
|
|
user = insert(:user, disclose_client: true)
|
|
%{user: _user, token: token, conn: conn} = oauth_access(["write:statuses"], user: user)
|
|
|
|
%Pleroma.Web.OAuth.Token{
|
|
app: %Pleroma.Web.OAuth.App{
|
|
client_name: app_name,
|
|
website: app_website
|
|
}
|
|
} = token
|
|
|
|
result =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe is my copilot"
|
|
})
|
|
|
|
assert %{
|
|
"content" => "cofe is my copilot"
|
|
} = json_response_and_validate_schema(result, 200)
|
|
|
|
activity = result.assigns.activity.id
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity}")
|
|
|
|
assert %{
|
|
"content" => "cofe is my copilot",
|
|
"application" => %{
|
|
"name" => ^app_name,
|
|
"website" => ^app_website
|
|
}
|
|
} = json_response_and_validate_schema(result, 200)
|
|
end
|
|
|
|
test "hides application metadata when disabled" do
|
|
user = insert(:user, disclose_client: false)
|
|
%{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
|
|
|
|
result =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "club mate is my wingman"
|
|
})
|
|
|
|
assert %{"content" => "club mate is my wingman"} =
|
|
json_response_and_validate_schema(result, 200)
|
|
|
|
activity = result.assigns.activity.id
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity}")
|
|
|
|
assert %{
|
|
"content" => "club mate is my wingman",
|
|
"application" => nil
|
|
} = json_response_and_validate_schema(result, 200)
|
|
end
|
|
end
|
|
|
|
describe "posting scheduled statuses" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "creates a scheduled activity", %{conn: conn} do
|
|
scheduled_at =
|
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "scheduled",
|
|
"scheduled_at" => scheduled_at
|
|
})
|
|
|
|
assert %{"scheduled_at" => expected_scheduled_at} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at)
|
|
assert [] == Repo.all(Activity)
|
|
end
|
|
|
|
test "with expiration" do
|
|
%{conn: conn} = oauth_access(["write:statuses", "read:statuses"])
|
|
|
|
scheduled_at =
|
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
assert %{"id" => status_id, "params" => %{"expires_in" => 300}} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "scheduled",
|
|
"scheduled_at" => scheduled_at,
|
|
"expires_in" => 300
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert %{"id" => ^status_id, "params" => %{"expires_in" => 300}} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> get("/api/v1/scheduled_statuses/#{status_id}")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
|
|
test "ignores nil values", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "not scheduled",
|
|
"scheduled_at" => nil
|
|
})
|
|
|
|
assert result = json_response_and_validate_schema(conn, 200)
|
|
assert Activity.get_by_id(result["id"])
|
|
end
|
|
|
|
test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
|
|
scheduled_at =
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.add(:timer.minutes(120), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
file = %Plug.Upload{
|
|
content_type: "image/jpeg",
|
|
path: Path.absname("test/fixtures/image.jpg"),
|
|
filename: "an_image.jpg"
|
|
}
|
|
|
|
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"media_ids" => [to_string(upload.id)],
|
|
"status" => "scheduled",
|
|
"scheduled_at" => scheduled_at
|
|
})
|
|
|
|
assert %{"media_attachments" => [media_attachment]} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert %{"type" => "image"} = media_attachment
|
|
end
|
|
|
|
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
|
|
%{conn: conn} do
|
|
scheduled_at =
|
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "not scheduled",
|
|
"scheduled_at" => scheduled_at
|
|
})
|
|
|
|
assert %{"content" => "not scheduled"} = json_response_and_validate_schema(conn, 200)
|
|
assert [] == Repo.all(ScheduledActivity)
|
|
end
|
|
|
|
test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do
|
|
today =
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
# TODO
|
|
|> Kernel.<>("Z")
|
|
|
|
attrs = %{params: %{}, scheduled_at: today}
|
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
|
|
|
|
assert %{"error" => "daily limit exceeded"} == json_response_and_validate_schema(conn, 422)
|
|
end
|
|
|
|
test "returns error when total user limit is exceeded", %{user: user, conn: conn} do
|
|
today =
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
tomorrow =
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
attrs = %{params: %{}, scheduled_at: today}
|
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
|
{:ok, _} = ScheduledActivity.create(user, attrs)
|
|
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
|
|
|
|
assert %{"error" => "total limit exceeded"} == json_response_and_validate_schema(conn, 422)
|
|
end
|
|
end
|
|
|
|
describe "posting polls" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "posting a poll", %{conn: conn} do
|
|
time = NaiveDateTime.utc_now()
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "Who is the #bestgrill?",
|
|
"poll" => %{
|
|
"options" => ["Rei", "Asuka", "Misato"],
|
|
"expires_in" => 420
|
|
}
|
|
})
|
|
|
|
response = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
|
|
title in ["Rei", "Asuka", "Misato"]
|
|
end)
|
|
|
|
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
|
|
assert response["poll"]["expired"] == false
|
|
|
|
question = Object.get_by_id(response["poll"]["id"])
|
|
|
|
# closed contains utc timezone
|
|
assert question.data["closed"] =~ "Z"
|
|
end
|
|
|
|
test "option limit is enforced", %{conn: conn} do
|
|
limit = Config.get([:instance, :poll_limits, :max_options])
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "desu~",
|
|
"poll" => %{
|
|
"options" => Enum.map(0..limit, fn num -> "desu #{num}" end),
|
|
"expires_in" => 1
|
|
}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Poll can't contain more than #{limit} options"
|
|
end
|
|
|
|
test "option character limit is enforced", %{conn: conn} do
|
|
limit = Config.get([:instance, :poll_limits, :max_option_chars])
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "...",
|
|
"poll" => %{
|
|
"options" => [String.duplicate(".", limit + 1), "lol"],
|
|
"expires_in" => 1
|
|
}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Poll options cannot be longer than #{limit} characters each"
|
|
end
|
|
|
|
test "minimal date limit is enforced", %{conn: conn} do
|
|
limit = Config.get([:instance, :poll_limits, :min_expiration])
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "imagine arbitrary limits",
|
|
"poll" => %{
|
|
"options" => ["this post was made by pleroma gang"],
|
|
"expires_in" => limit - 1
|
|
}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Expiration date is too soon"
|
|
end
|
|
|
|
test "maximum date limit is enforced", %{conn: conn} do
|
|
limit = Config.get([:instance, :poll_limits, :max_expiration])
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "imagine arbitrary limits",
|
|
"poll" => %{
|
|
"options" => ["this post was made by pleroma gang"],
|
|
"expires_in" => limit + 1
|
|
}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Expiration date is too far in the future"
|
|
end
|
|
|
|
test "scheduled poll", %{conn: conn} do
|
|
clear_config([ScheduledActivity, :enabled], true)
|
|
|
|
scheduled_at =
|
|
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(6), :millisecond)
|
|
|> NaiveDateTime.to_iso8601()
|
|
|> Kernel.<>("Z")
|
|
|
|
%{"id" => scheduled_id} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "very cool poll",
|
|
"poll" => %{
|
|
"options" => ~w(a b c),
|
|
"expires_in" => 420
|
|
},
|
|
"scheduled_at" => scheduled_at
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert {:ok, %{id: activity_id}} =
|
|
perform_job(ScheduledActivityWorker, %{
|
|
activity_id: scheduled_id
|
|
})
|
|
|
|
refute_enqueued(worker: ScheduledActivityWorker)
|
|
|
|
object =
|
|
Activity
|
|
|> Repo.get(activity_id)
|
|
|> Object.normalize()
|
|
|
|
assert object.data["content"] == "very cool poll"
|
|
assert object.data["type"] == "Question"
|
|
assert length(object.data["oneOf"]) == 3
|
|
end
|
|
|
|
test "cannot have only one option", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "desu~",
|
|
"poll" => %{"options" => ["mew"], "expires_in" => 1}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Poll must contain at least 2 options"
|
|
end
|
|
|
|
test "cannot have only duplicated options", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "desu~",
|
|
"poll" => %{"options" => ["mew", "mew"], "expires_in" => 1}
|
|
})
|
|
|
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
|
assert error == "Poll must contain at least 2 options"
|
|
end
|
|
end
|
|
|
|
test "get a status" do
|
|
%{conn: conn} = oauth_access(["read:statuses"])
|
|
activity = insert(:note_activity)
|
|
|
|
conn = get(conn, "/api/v1/statuses/#{activity.id}")
|
|
|
|
assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
|
|
assert id == to_string(activity.id)
|
|
end
|
|
|
|
defp local_and_remote_activities do
|
|
local = insert(:note_activity)
|
|
remote = insert(:note_activity, local: false)
|
|
{:ok, local: local, remote: remote}
|
|
end
|
|
|
|
describe "status with restrict unauthenticated activities for local and remote" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
|
|
assert json_response_and_validate_schema(res_conn, :not_found) == %{
|
|
"error" => "Record not found"
|
|
}
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
|
|
assert json_response_and_validate_schema(res_conn, :not_found) == %{
|
|
"error" => "Record not found"
|
|
}
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
end
|
|
|
|
describe "status with restrict unauthenticated activities for local" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
|
|
assert json_response_and_validate_schema(res_conn, :not_found) == %{
|
|
"error" => "Record not found"
|
|
}
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
end
|
|
|
|
describe "status with restrict unauthenticated activities for remote" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
|
|
assert json_response_and_validate_schema(res_conn, :not_found) == %{
|
|
"error" => "Record not found"
|
|
}
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
res_conn = get(conn, "/api/v1/statuses/#{local.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
res_conn = get(conn, "/api/v1/statuses/#{remote.id}")
|
|
assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
end
|
|
|
|
test "getting a status that doesn't exist returns 404" do
|
|
%{conn: conn} = oauth_access(["read:statuses"])
|
|
activity = insert(:note_activity)
|
|
|
|
conn = get(conn, "/api/v1/statuses/#{String.downcase(activity.id)}")
|
|
|
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
|
end
|
|
|
|
test "get a direct status" do
|
|
%{user: user, conn: conn} = oauth_access(["read:statuses"])
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{status: "@#{other_user.nickname}", visibility: "direct"})
|
|
|
|
conn =
|
|
conn
|
|
|> assign(:user, user)
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|
|
[participation] = Participation.for_user(user)
|
|
|
|
res = json_response_and_validate_schema(conn, 200)
|
|
assert res["pleroma"]["direct_conversation_id"] == participation.id
|
|
end
|
|
|
|
test "get statuses by IDs" do
|
|
%{conn: conn} = oauth_access(["read:statuses"])
|
|
%{id: id1} = insert(:note_activity)
|
|
%{id: id2} = insert(:note_activity)
|
|
|
|
query_string = "ids[]=#{id1}&ids[]=#{id2}"
|
|
conn = get(conn, "/api/v1/statuses/?#{query_string}")
|
|
|
|
assert [%{"id" => ^id1}, %{"id" => ^id2}] =
|
|
Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"])
|
|
end
|
|
|
|
describe "getting statuses by ids with restricted unauthenticated for local and remote" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
assert json_response_and_validate_schema(res_conn, 200) == []
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
|
|
end
|
|
end
|
|
|
|
describe "getting statuses by ids with restricted unauthenticated for local" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :local], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
remote_id = remote.id
|
|
assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
|
|
end
|
|
end
|
|
|
|
describe "getting statuses by ids with restricted unauthenticated for remote" do
|
|
setup do: local_and_remote_activities()
|
|
|
|
setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true)
|
|
|
|
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
local_id = local.id
|
|
assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200)
|
|
end
|
|
|
|
test "if user is authenticated", %{local: local, remote: remote} do
|
|
%{conn: conn} = oauth_access(["read"])
|
|
|
|
res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}")
|
|
|
|
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
|
|
end
|
|
end
|
|
|
|
describe "deleting a status" do
|
|
test "when you created it" do
|
|
%{user: author, conn: conn} = oauth_access(["write:statuses"])
|
|
activity = insert(:note_activity, user: author)
|
|
object = Object.normalize(activity, fetch: false)
|
|
|
|
content = object.data["content"]
|
|
source = object.data["source"]
|
|
|
|
result =
|
|
conn
|
|
|> assign(:user, author)
|
|
|> delete("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert match?(%{"content" => ^content, "text" => ^source}, result)
|
|
|
|
refute Activity.get_by_id(activity.id)
|
|
end
|
|
|
|
test "when it doesn't exist" do
|
|
%{user: author, conn: conn} = oauth_access(["write:statuses"])
|
|
activity = insert(:note_activity, user: author)
|
|
|
|
conn =
|
|
conn
|
|
|> assign(:user, author)
|
|
|> delete("/api/v1/statuses/#{String.downcase(activity.id)}")
|
|
|
|
assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
|
|
end
|
|
|
|
test "when you didn't create it" do
|
|
%{conn: conn} = oauth_access(["write:statuses"])
|
|
activity = insert(:note_activity)
|
|
|
|
conn = delete(conn, "/api/v1/statuses/#{activity.id}")
|
|
|
|
assert %{"error" => "Record not found"} == json_response_and_validate_schema(conn, 404)
|
|
|
|
assert Activity.get_by_id(activity.id) == activity
|
|
end
|
|
|
|
test "when you're an admin or moderator", %{conn: conn} do
|
|
activity1 = insert(:note_activity)
|
|
activity2 = insert(:note_activity)
|
|
admin = insert(:user, is_admin: true)
|
|
moderator = insert(:user, is_moderator: true)
|
|
|
|
res_conn =
|
|
conn
|
|
|> assign(:user, admin)
|
|
|> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"]))
|
|
|> delete("/api/v1/statuses/#{activity1.id}")
|
|
|
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
res_conn =
|
|
conn
|
|
|> assign(:user, moderator)
|
|
|> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"]))
|
|
|> delete("/api/v1/statuses/#{activity2.id}")
|
|
|
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
refute Activity.get_by_id(activity1.id)
|
|
refute Activity.get_by_id(activity2.id)
|
|
end
|
|
|
|
test "when you're privileged and the user is banned", %{conn: conn} do
|
|
clear_config([:instance, :moderator_privileges], [:messages_delete])
|
|
posting_user = insert(:user, is_active: false)
|
|
refute posting_user.is_active
|
|
activity = insert(:note_activity, user: posting_user)
|
|
user = insert(:user, is_moderator: true)
|
|
|
|
res_conn =
|
|
conn
|
|
|> assign(:user, user)
|
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
|
|
|> delete("/api/v1/statuses/#{activity.id}")
|
|
|
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
|
|
|
# assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
|
|
# "@#{user.nickname} deleted status ##{activity.id}"
|
|
|
|
refute Activity.get_by_id(activity.id)
|
|
end
|
|
end
|
|
|
|
describe "reblogging" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "reblogs and returns the reblogged status", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
|
|
|
assert %{
|
|
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
|
|
"reblogged" => true
|
|
} = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "returns 404 if the reblogged status doesn't exist", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{String.downcase(activity.id)}/reblog")
|
|
|
|
assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404)
|
|
end
|
|
|
|
test "reblogs privately and returns the reblogged status", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post(
|
|
"/api/v1/statuses/#{activity.id}/reblog",
|
|
%{"visibility" => "private"}
|
|
)
|
|
|
|
assert %{
|
|
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
|
|
"reblogged" => true,
|
|
"visibility" => "private"
|
|
} = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "reblogged status for another user" do
|
|
activity = insert(:note_activity)
|
|
user1 = insert(:user)
|
|
user2 = insert(:user)
|
|
user3 = insert(:user)
|
|
{:ok, _} = CommonAPI.favorite(user2, activity.id)
|
|
{:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
|
|
{:ok, reblog_activity1} = CommonAPI.repeat(activity.id, user1)
|
|
{:ok, _} = CommonAPI.repeat(activity.id, user2)
|
|
|
|
conn_res =
|
|
build_conn()
|
|
|> assign(:user, user3)
|
|
|> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
|
|
|> get("/api/v1/statuses/#{reblog_activity1.id}")
|
|
|
|
assert %{
|
|
"reblog" => %{"id" => _id, "reblogged" => false, "reblogs_count" => 2},
|
|
"reblogged" => false,
|
|
"favourited" => false,
|
|
"bookmarked" => false
|
|
} = json_response_and_validate_schema(conn_res, 200)
|
|
|
|
conn_res =
|
|
build_conn()
|
|
|> assign(:user, user2)
|
|
|> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"]))
|
|
|> get("/api/v1/statuses/#{reblog_activity1.id}")
|
|
|
|
assert %{
|
|
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
|
|
"reblogged" => true,
|
|
"favourited" => true,
|
|
"bookmarked" => true
|
|
} = json_response_and_validate_schema(conn_res, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "author can reblog own private status", %{conn: conn, user: user} do
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "cofe", visibility: "private"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
|
|
|
assert %{
|
|
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
|
|
"reblogged" => true,
|
|
"visibility" => "private"
|
|
} = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
end
|
|
|
|
describe "unreblogging" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
{:ok, _} = CommonAPI.repeat(activity.id, user)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/unreblog")
|
|
|
|
assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "returns 404 error when activity does not exist", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/foo/unreblog")
|
|
|
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
|
end
|
|
end
|
|
|
|
describe "favoriting" do
|
|
setup do: oauth_access(["write:favourites"])
|
|
|
|
test "favs a status and returns it", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
|
|
|
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "favoriting twice will just return 200", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
|
|
|
assert conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/favourite")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
|
|
test "returns 404 error for a wrong id", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/1/favourite")
|
|
|
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
|
end
|
|
end
|
|
|
|
describe "unfavoriting" do
|
|
setup do: oauth_access(["write:favourites"])
|
|
|
|
test "unfavorites a status and returns it", %{user: user, conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
{:ok, _} = CommonAPI.favorite(user, activity.id)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
|
|
|
|
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
end
|
|
|
|
test "returns 404 error for a wrong id", %{conn: conn} do
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/1/unfavourite")
|
|
|
|
assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
|
|
end
|
|
end
|
|
|
|
describe "pinned statuses" do
|
|
setup do: oauth_access(["write:accounts"])
|
|
|
|
setup %{user: user} do
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "HI!!!"})
|
|
|
|
%{activity: activity}
|
|
end
|
|
|
|
setup do: clear_config([:instance, :max_pinned_statuses], 1)
|
|
|
|
test "pin status", %{conn: conn, user: user, activity: activity} do
|
|
id = activity.id
|
|
|
|
assert %{"id" => ^id, "pinned" => true} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/pin")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [%{"id" => ^id, "pinned" => true}] =
|
|
conn
|
|
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
|
|
test "non authenticated user", %{activity: activity} do
|
|
assert build_conn()
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/pin")
|
|
|> json_response(403) == %{"error" => "Invalid credentials."}
|
|
end
|
|
|
|
test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do
|
|
{:ok, dm} = CommonAPI.post(user, %{status: "test", visibility: "direct"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{dm.id}/pin")
|
|
|
|
assert json_response_and_validate_schema(conn, 422) == %{
|
|
"error" => "Non-public status cannot be pinned"
|
|
}
|
|
end
|
|
|
|
test "pin by another user", %{activity: activity} do
|
|
%{conn: conn} = oauth_access(["write:accounts"])
|
|
|
|
assert conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/pin")
|
|
|> json_response(422) == %{"error" => "Someone else's status cannot be pinned"}
|
|
end
|
|
|
|
test "unpin status", %{conn: conn, user: user, activity: activity} do
|
|
{:ok, _} = CommonAPI.pin(activity.id, user)
|
|
user = refresh_record(user)
|
|
|
|
id_str = to_string(activity.id)
|
|
|
|
assert %{"id" => ^id_str, "pinned" => false} =
|
|
conn
|
|
|> assign(:user, user)
|
|
|> post("/api/v1/statuses/#{activity.id}/unpin")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [] =
|
|
conn
|
|
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
|
|
test "/unpin: returns 404 error when activity doesn't exist", %{conn: conn} do
|
|
assert conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/1/unpin")
|
|
|> json_response_and_validate_schema(404) == %{"error" => "Record not found"}
|
|
end
|
|
|
|
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
|
|
{:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
|
|
|
|
id_str_one = to_string(activity_one.id)
|
|
|
|
assert %{"id" => ^id_str_one, "pinned" => true} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{id_str_one}/pin")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
user = refresh_record(user)
|
|
|
|
assert %{"error" => "You have already pinned the maximum number of statuses"} =
|
|
conn
|
|
|> assign(:user, user)
|
|
|> post("/api/v1/statuses/#{activity_two.id}/pin")
|
|
|> json_response_and_validate_schema(400)
|
|
end
|
|
|
|
test "on pin removes deletion job, on unpin reschedule deletion" do
|
|
%{conn: conn} = oauth_access(["write:accounts", "write:statuses"])
|
|
expires_in = 2 * 60 * 60
|
|
|
|
expires_at = DateTime.add(DateTime.utc_now(), expires_in)
|
|
|
|
assert %{"id" => id} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "oolong",
|
|
"expires_in" => expires_in
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: expires_at
|
|
)
|
|
|
|
assert %{"id" => ^id, "pinned" => true} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{id}/pin")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
refute_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: expires_at
|
|
)
|
|
|
|
assert %{"id" => ^id, "pinned" => false} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{id}/unpin")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: expires_at
|
|
)
|
|
end
|
|
end
|
|
|
|
test "bookmarks" do
|
|
bookmarks_uri = "/api/v1/bookmarks"
|
|
|
|
%{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
|
|
author = insert(:user)
|
|
|
|
{:ok, activity1} = CommonAPI.post(author, %{status: "heweoo?"})
|
|
{:ok, activity2} = CommonAPI.post(author, %{status: "heweoo!"})
|
|
|
|
response1 =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity1.id}/bookmark")
|
|
|
|
assert json_response_and_validate_schema(response1, 200)["bookmarked"] == true
|
|
|
|
response2 =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity2.id}/bookmark")
|
|
|
|
assert json_response_and_validate_schema(response2, 200)["bookmarked"] == true
|
|
|
|
bookmarks = get(conn, bookmarks_uri)
|
|
|
|
assert [
|
|
json_response_and_validate_schema(response2, 200),
|
|
json_response_and_validate_schema(response1, 200)
|
|
] ==
|
|
json_response_and_validate_schema(bookmarks, 200)
|
|
|
|
response1 =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity1.id}/unbookmark")
|
|
|
|
assert json_response_and_validate_schema(response1, 200)["bookmarked"] == false
|
|
|
|
bookmarks = get(conn, bookmarks_uri)
|
|
|
|
assert [json_response_and_validate_schema(response2, 200)] ==
|
|
json_response_and_validate_schema(bookmarks, 200)
|
|
end
|
|
|
|
describe "conversation muting" do
|
|
setup do: oauth_access(["write:mutes"])
|
|
|
|
setup do
|
|
post_user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(post_user, %{status: "HIE"})
|
|
%{activity: activity}
|
|
end
|
|
|
|
test "mute conversation", %{conn: conn, activity: activity} do
|
|
id_str = to_string(activity.id)
|
|
|
|
assert %{"id" => ^id_str, "muted" => true} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/mute")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
|
|
test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do
|
|
{:ok, _} = CommonAPI.add_mute(user, activity)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/mute")
|
|
|
|
assert json_response_and_validate_schema(conn, 400) == %{
|
|
"error" => "conversation is already muted"
|
|
}
|
|
end
|
|
|
|
test "unmute conversation", %{conn: conn, user: user, activity: activity} do
|
|
{:ok, _} = CommonAPI.add_mute(user, activity)
|
|
|
|
id_str = to_string(activity.id)
|
|
|
|
assert %{"id" => ^id_str, "muted" => false} =
|
|
conn
|
|
# |> assign(:user, user)
|
|
|> post("/api/v1/statuses/#{activity.id}/unmute")
|
|
|> json_response_and_validate_schema(200)
|
|
end
|
|
end
|
|
|
|
test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
|
|
user1 = insert(:user)
|
|
user2 = insert(:user)
|
|
user3 = insert(:user)
|
|
|
|
{:ok, replied_to} = CommonAPI.post(user1, %{status: "cofe"})
|
|
|
|
# Reply to status from another user
|
|
conn1 =
|
|
conn
|
|
|> assign(:user, user2)
|
|
|> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"]))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
|
|
|
|
assert %{"content" => "xD", "id" => id} = json_response_and_validate_schema(conn1, 200)
|
|
|
|
activity = Activity.get_by_id_with_object(id)
|
|
|
|
assert Object.normalize(activity, fetch: false).data["inReplyTo"] ==
|
|
Object.normalize(replied_to, fetch: false).data["id"]
|
|
|
|
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
|
|
|
|
# Reblog from the third user
|
|
conn2 =
|
|
conn
|
|
|> assign(:user, user3)
|
|
|> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"]))
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses/#{activity.id}/reblog")
|
|
|
|
assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
|
|
json_response_and_validate_schema(conn2, 200)
|
|
|
|
assert to_string(activity.id) == id
|
|
|
|
# Getting third user status
|
|
conn3 =
|
|
conn
|
|
|> assign(:user, user3)
|
|
|> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
|
|
|> get("/api/v1/timelines/home")
|
|
|
|
[reblogged_activity] = json_response_and_validate_schema(conn3, 200)
|
|
|
|
assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
|
|
|
|
replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
|
|
assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
|
|
end
|
|
|
|
describe "GET /api/v1/statuses/:id/favourited_by" do
|
|
setup do: oauth_access(["read:accounts"])
|
|
|
|
setup %{user: user} do
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "test"})
|
|
|
|
%{activity: activity}
|
|
end
|
|
|
|
test "returns users who have favorited the status", %{conn: conn, activity: activity} do
|
|
other_user = insert(:user)
|
|
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
[%{"id" => id}] = response
|
|
|
|
assert id == other_user.id
|
|
end
|
|
|
|
test "returns empty array when status has not been favorited yet", %{
|
|
conn: conn,
|
|
activity: activity
|
|
} do
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
|
|
test "does not return users who have favorited the status but are blocked", %{
|
|
conn: %{assigns: %{user: user}} = conn,
|
|
activity: activity
|
|
} do
|
|
other_user = insert(:user)
|
|
{:ok, _user_relationship} = User.block(user, other_user)
|
|
|
|
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
|
|
test "does not fail on an unauthenticated request", %{activity: activity} do
|
|
other_user = insert(:user)
|
|
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
|
|
|
response =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
[%{"id" => id}] = response
|
|
assert id == other_user.id
|
|
end
|
|
|
|
test "requires authentication for private posts", %{user: user} do
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: "@#{other_user.nickname} wanna get some #cofe together?",
|
|
visibility: "direct"
|
|
})
|
|
|
|
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
|
|
|
favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
|
|
|
|
build_conn()
|
|
|> get(favourited_by_url)
|
|
|> json_response_and_validate_schema(404)
|
|
|
|
conn =
|
|
build_conn()
|
|
|> assign(:user, other_user)
|
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
|
|
|
|
conn
|
|
|> assign(:token, nil)
|
|
|> get(favourited_by_url)
|
|
|> json_response_and_validate_schema(404)
|
|
|
|
response =
|
|
conn
|
|
|> get(favourited_by_url)
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
[%{"id" => id}] = response
|
|
assert id == other_user.id
|
|
end
|
|
|
|
test "returns empty array when :show_reactions is disabled", %{conn: conn, activity: activity} do
|
|
clear_config([:instance, :show_reactions], false)
|
|
|
|
other_user = insert(:user)
|
|
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
end
|
|
|
|
describe "GET /api/v1/statuses/:id/reblogged_by" do
|
|
setup do: oauth_access(["read:accounts"])
|
|
|
|
setup %{user: user} do
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "test"})
|
|
|
|
%{activity: activity}
|
|
end
|
|
|
|
test "returns users who have reblogged the status", %{conn: conn, activity: activity} do
|
|
other_user = insert(:user)
|
|
{:ok, _} = CommonAPI.repeat(activity.id, other_user)
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
[%{"id" => id}] = response
|
|
|
|
assert id == other_user.id
|
|
end
|
|
|
|
test "returns empty array when status has not been reblogged yet", %{
|
|
conn: conn,
|
|
activity: activity
|
|
} do
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
|
|
test "does not return users who have reblogged the status but are blocked", %{
|
|
conn: %{assigns: %{user: user}} = conn,
|
|
activity: activity
|
|
} do
|
|
other_user = insert(:user)
|
|
{:ok, _user_relationship} = User.block(user, other_user)
|
|
|
|
{:ok, _} = CommonAPI.repeat(activity.id, other_user)
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
|
|
test "does not return users who have reblogged the status privately", %{
|
|
conn: conn
|
|
} do
|
|
other_user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "my secret post"})
|
|
|
|
{:ok, _} = CommonAPI.repeat(activity.id, other_user, %{visibility: "private"})
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert Enum.empty?(response)
|
|
end
|
|
|
|
test "does not fail on an unauthenticated request", %{activity: activity} do
|
|
other_user = insert(:user)
|
|
{:ok, _} = CommonAPI.repeat(activity.id, other_user)
|
|
|
|
response =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
[%{"id" => id}] = response
|
|
assert id == other_user.id
|
|
end
|
|
|
|
test "requires authentication for private posts", %{user: user} do
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: "@#{other_user.nickname} wanna get some #cofe together?",
|
|
visibility: "direct"
|
|
})
|
|
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(404)
|
|
|
|
response =
|
|
build_conn()
|
|
|> assign(:user, other_user)
|
|
|> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
|
|
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [] == response
|
|
end
|
|
end
|
|
|
|
test "context" do
|
|
user = insert(:user)
|
|
|
|
{:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
|
|
{:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
|
|
{:ok, %{id: id3}} = CommonAPI.post(user, %{status: "3", in_reply_to_status_id: id2})
|
|
{:ok, %{id: id4}} = CommonAPI.post(user, %{status: "4", in_reply_to_status_id: id3})
|
|
{:ok, %{id: id5}} = CommonAPI.post(user, %{status: "5", in_reply_to_status_id: id4})
|
|
|
|
response =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{id3}/context")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert %{
|
|
"ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}],
|
|
"descendants" => [%{"id" => ^id4}, %{"id" => ^id5}]
|
|
} = response
|
|
end
|
|
|
|
test "context when restrict_unauthenticated is on" do
|
|
user = insert(:user)
|
|
remote_user = insert(:user, local: false)
|
|
|
|
{:ok, %{id: id1}} = CommonAPI.post(user, %{status: "1"})
|
|
{:ok, %{id: id2}} = CommonAPI.post(user, %{status: "2", in_reply_to_status_id: id1})
|
|
|
|
{:ok, %{id: id3}} =
|
|
CommonAPI.post(remote_user, %{status: "3", in_reply_to_status_id: id2, local: false})
|
|
|
|
response =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{id2}/context")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert %{
|
|
"ancestors" => [%{"id" => ^id1}],
|
|
"descendants" => [%{"id" => ^id3}]
|
|
} = response
|
|
|
|
clear_config([:restrict_unauthenticated, :activities, :local], true)
|
|
|
|
response =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{id2}/context")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert %{
|
|
"ancestors" => [],
|
|
"descendants" => []
|
|
} = response
|
|
end
|
|
|
|
test "favorites paginate correctly" do
|
|
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
|
other_user = insert(:user)
|
|
{:ok, first_post} = CommonAPI.post(other_user, %{status: "bla"})
|
|
{:ok, second_post} = CommonAPI.post(other_user, %{status: "bla"})
|
|
{:ok, third_post} = CommonAPI.post(other_user, %{status: "bla"})
|
|
|
|
{:ok, _first_favorite} = CommonAPI.favorite(user, third_post.id)
|
|
{:ok, _second_favorite} = CommonAPI.favorite(user, first_post.id)
|
|
{:ok, third_favorite} = CommonAPI.favorite(user, second_post.id)
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/favourites?limit=1")
|
|
|
|
assert [%{"id" => post_id}] = json_response_and_validate_schema(result, 200)
|
|
assert post_id == second_post.id
|
|
|
|
# Using the header for pagination works correctly
|
|
[next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ")
|
|
[_, max_id] = Regex.run(~r/max_id=([^&]+)/, next)
|
|
|
|
assert max_id == third_favorite.id
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/favourites?max_id=#{max_id}")
|
|
|
|
assert [%{"id" => first_post_id}, %{"id" => third_post_id}] =
|
|
json_response_and_validate_schema(result, 200)
|
|
|
|
assert first_post_id == first_post.id
|
|
assert third_post_id == third_post.id
|
|
end
|
|
|
|
test "returns the favorites of a user" do
|
|
%{user: user, conn: conn} = oauth_access(["read:favourites"])
|
|
other_user = insert(:user)
|
|
|
|
{:ok, _} = CommonAPI.post(other_user, %{status: "bla"})
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "trees are happy"})
|
|
|
|
{:ok, last_like} = CommonAPI.favorite(user, activity.id)
|
|
|
|
first_conn = get(conn, "/api/v1/favourites")
|
|
|
|
assert [status] = json_response_and_validate_schema(first_conn, 200)
|
|
assert status["id"] == to_string(activity.id)
|
|
|
|
assert [{"link", _link_header}] =
|
|
Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
|
|
|
|
# Honours query params
|
|
{:ok, second_activity} =
|
|
CommonAPI.post(other_user, %{
|
|
status: "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
|
|
})
|
|
|
|
{:ok, _} = CommonAPI.favorite(user, second_activity.id)
|
|
|
|
second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like.id}")
|
|
|
|
assert [second_status] = json_response_and_validate_schema(second_conn, 200)
|
|
assert second_status["id"] == to_string(second_activity.id)
|
|
|
|
third_conn = get(conn, "/api/v1/favourites?limit=0")
|
|
|
|
assert [] = json_response_and_validate_schema(third_conn, 200)
|
|
end
|
|
|
|
test "expires_at is nil for another user" do
|
|
%{conn: conn, user: user} = oauth_access(["read:statuses"])
|
|
expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
|
|
|
|
assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
{:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
|
|
assert DateTime.diff(expires_at, a_expires_at) == 0
|
|
|
|
%{conn: conn} = oauth_access(["read:statuses"])
|
|
|
|
assert %{"pleroma" => %{"expires_at" => nil}} =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(:ok)
|
|
end
|
|
|
|
describe "local-only statuses" do
|
|
test "posting a local only status" do
|
|
%{user: _user, conn: conn} = oauth_access(["write:statuses"])
|
|
|
|
conn_one =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "cofe",
|
|
"visibility" => "local"
|
|
})
|
|
|
|
local = Utils.as_local_public()
|
|
|
|
assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
|
|
json_response_and_validate_schema(conn_one, 200)
|
|
|
|
assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
|
|
end
|
|
|
|
test "other users can read local-only posts" do
|
|
user = insert(:user)
|
|
%{user: _reader, conn: conn} = oauth_access(["read:statuses"])
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
|
|
|
received =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(:ok)
|
|
|
|
assert received["id"] == activity.id
|
|
end
|
|
|
|
test "anonymous users cannot see local-only posts" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU", visibility: "local"})
|
|
|
|
_received =
|
|
build_conn()
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(:not_found)
|
|
end
|
|
end
|
|
|
|
describe "muted reactions" do
|
|
test "index" do
|
|
%{conn: conn, user: user} = oauth_access(["read:statuses"])
|
|
|
|
other_user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "test"})
|
|
|
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
|
User.mute(user, other_user)
|
|
|
|
deactivated_user = insert(:user)
|
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, deactivated_user, "🎅")
|
|
User.set_activation(deactivated_user, false)
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/?ids[]=#{activity.id}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [
|
|
%{
|
|
"emoji_reactions" => [],
|
|
"pleroma" => %{
|
|
"emoji_reactions" => []
|
|
}
|
|
}
|
|
] = result
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [
|
|
%{
|
|
"pleroma" => %{
|
|
"emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
|
|
}
|
|
}
|
|
] = result
|
|
end
|
|
|
|
test "show" do
|
|
# %{conn: conn, user: user, token: token} = oauth_access(["read:statuses"])
|
|
%{conn: conn, user: user, token: _token} = oauth_access(["read:statuses"])
|
|
|
|
other_user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "test"})
|
|
|
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅")
|
|
User.mute(user, other_user)
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert %{
|
|
"pleroma" => %{
|
|
"emoji_reactions" => []
|
|
}
|
|
} = result
|
|
|
|
result =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}?with_muted=true")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert %{
|
|
"pleroma" => %{
|
|
"emoji_reactions" => [%{"count" => 1, "me" => false, "name" => "🎅"}]
|
|
}
|
|
} = result
|
|
end
|
|
end
|
|
|
|
describe "posting quotes" do
|
|
setup do: oauth_access(["write:statuses"])
|
|
|
|
test "posting a quote", %{conn: conn} do
|
|
user = insert(:user)
|
|
{:ok, quoted_status} = CommonAPI.post(user, %{status: "tell me, for whom do you fight?"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "Hmph, how very glib",
|
|
"quote_id" => quoted_status.id
|
|
})
|
|
|
|
response = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert response["quote_id"] == quoted_status.id
|
|
assert response["quote"]["id"] == quoted_status.id
|
|
assert response["quote"]["content"] == quoted_status.object.data["content"]
|
|
assert response["pleroma"]["context"] == quoted_status.data["context"]
|
|
end
|
|
|
|
test "posting a quote, quoting a status that isn't public", %{conn: conn} do
|
|
user = insert(:user)
|
|
|
|
Enum.each(["private", "local", "direct"], fn visibility ->
|
|
{:ok, quoted_status} =
|
|
CommonAPI.post(user, %{
|
|
status: "tell me, for whom do you fight?",
|
|
visibility: visibility
|
|
})
|
|
|
|
assert %{"error" => "You can only quote public or unlisted statuses"} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "Hmph, how very glib",
|
|
"quote_id" => quoted_status.id
|
|
})
|
|
|> json_response_and_validate_schema(422)
|
|
end)
|
|
end
|
|
|
|
test "posting a quote, after quote, the status gets deleted", %{conn: conn} do
|
|
user = insert(:user)
|
|
|
|
{:ok, quoted_status} =
|
|
CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
|
|
|
|
resp =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "I fight for eorzea!",
|
|
"quote_id" => quoted_status.id
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
{:ok, _} = CommonAPI.delete(quoted_status.id, user)
|
|
|
|
resp =
|
|
conn
|
|
|> get("/api/v1/statuses/#{resp["id"]}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert is_nil(resp["quote"])
|
|
end
|
|
|
|
test "posting a quote of a deleted status", %{conn: conn} do
|
|
user = insert(:user)
|
|
|
|
{:ok, quoted_status} =
|
|
CommonAPI.post(user, %{status: "tell me, for whom do you fight?", visibility: "public"})
|
|
|
|
{:ok, _} = CommonAPI.delete(quoted_status.id, user)
|
|
|
|
assert %{"error" => _} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "I fight for eorzea!",
|
|
"quote_id" => quoted_status.id
|
|
})
|
|
|> json_response_and_validate_schema(422)
|
|
end
|
|
|
|
test "posting a quote of a status that doesn't exist", %{conn: conn} do
|
|
assert %{"error" => "You can't quote a status that doesn't exist"} =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "I fight for eorzea!",
|
|
"quote_id" => "oops"
|
|
})
|
|
|> json_response_and_validate_schema(422)
|
|
end
|
|
end
|
|
|
|
describe "get status history" do
|
|
setup do
|
|
%{conn: build_conn()}
|
|
end
|
|
|
|
test "unedited post", %{conn: conn} do
|
|
activity = insert(:note_activity)
|
|
|
|
conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
|
|
|
|
assert [_] = json_response_and_validate_schema(conn, 200)
|
|
end
|
|
|
|
test "edited post", %{conn: conn} do
|
|
note =
|
|
insert(
|
|
:note,
|
|
data: %{
|
|
"formerRepresentations" => %{
|
|
"type" => "OrderedCollection",
|
|
"orderedItems" => [
|
|
%{
|
|
"type" => "Note",
|
|
"content" => "mew mew 2",
|
|
"summary" => "title 2"
|
|
},
|
|
%{
|
|
"type" => "Note",
|
|
"content" => "mew mew 1",
|
|
"summary" => "title 1"
|
|
}
|
|
],
|
|
"totalItems" => 2
|
|
}
|
|
}
|
|
)
|
|
|
|
activity = insert(:note_activity, note: note)
|
|
|
|
conn = get(conn, "/api/v1/statuses/#{activity.id}/history")
|
|
|
|
assert [%{"spoiler_text" => "title 1"}, %{"spoiler_text" => "title 2"}, _] =
|
|
json_response_and_validate_schema(conn, 200)
|
|
end
|
|
end
|
|
|
|
describe "translating statuses" do
|
|
setup do
|
|
clear_config([:translator, :enabled], true)
|
|
clear_config([:translator, :module], Pleroma.Akkoma.Translators.DeepL)
|
|
clear_config([:deepl, :api_key], "deepl_api_key")
|
|
oauth_access(["read:statuses"])
|
|
end
|
|
|
|
test "listing languages", %{conn: conn} do
|
|
Tesla.Mock.mock_global(fn
|
|
%{method: :get, url: "https://api-free.deepl.com/v2/languages?type=source"} ->
|
|
%Tesla.Env{
|
|
status: 200,
|
|
body:
|
|
Jason.encode!([
|
|
%{language: "en", name: "English"}
|
|
])
|
|
}
|
|
|
|
%{method: :get, url: "https://api-free.deepl.com/v2/languages?type=target"} ->
|
|
%Tesla.Env{
|
|
status: 200,
|
|
body:
|
|
Jason.encode!([
|
|
%{language: "ja", name: "Japanese"}
|
|
])
|
|
}
|
|
end)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> get("/api/v1/akkoma/translation/languages")
|
|
|
|
response = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert %{
|
|
"source" => [%{"code" => "en", "name" => "English"}],
|
|
"target" => [%{"code" => "ja", "name" => "Japanese"}]
|
|
} = response
|
|
end
|
|
|
|
test "should return text and detected language", %{conn: conn} do
|
|
clear_config([:deepl, :tier], :free)
|
|
|
|
Tesla.Mock.mock_global(fn
|
|
%{method: :post, url: "https://api-free.deepl.com/v2/translate"} ->
|
|
%Tesla.Env{
|
|
status: 200,
|
|
body:
|
|
Jason.encode!(%{
|
|
translations: [
|
|
%{
|
|
"text" => "Tell me, for whom do you fight?",
|
|
"detected_source_language" => "ja"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
end)
|
|
|
|
user = insert(:user)
|
|
{:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> get("/api/v1/statuses/#{to_translate.id}/translations/en")
|
|
|
|
response = json_response_and_validate_schema(conn, 200)
|
|
|
|
assert response["text"] == "Tell me, for whom do you fight?"
|
|
assert response["detected_language"] == "ja"
|
|
end
|
|
|
|
test "should not allow translating of statuses you cannot see", %{conn: conn} do
|
|
clear_config([:deepl, :tier], :free)
|
|
|
|
Tesla.Mock.mock_global(fn
|
|
%{method: :post, url: "https://api-free.deepl.com/v2/translate"} ->
|
|
%Tesla.Env{
|
|
status: 200,
|
|
body:
|
|
Jason.encode!(%{
|
|
translations: [
|
|
%{
|
|
"text" => "Tell me, for whom do you fight?",
|
|
"detected_source_language" => "ja"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
end)
|
|
|
|
user = insert(:user)
|
|
{:ok, to_translate} = CommonAPI.post(user, %{status: "何のために闘う?", visibility: "private"})
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> get("/api/v1/statuses/#{to_translate.id}/translations/en")
|
|
|
|
json_response_and_validate_schema(conn, 404)
|
|
end
|
|
end
|
|
|
|
describe "get status source" do
|
|
setup do
|
|
%{conn: build_conn()}
|
|
end
|
|
|
|
test "it returns the source", %{conn: conn} do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
|
|
|
|
conn = get(conn, "/api/v1/statuses/#{activity.id}/source")
|
|
|
|
id = activity.id
|
|
|
|
assert %{"id" => ^id, "text" => "mew mew #abc", "spoiler_text" => "#def"} =
|
|
json_response_and_validate_schema(conn, 200)
|
|
end
|
|
end
|
|
|
|
describe "update status" do
|
|
setup do
|
|
oauth_access(["write:statuses"])
|
|
end
|
|
|
|
test "it updates the status" do
|
|
%{conn: conn, user: user} = oauth_access(["write:statuses", "read:statuses"])
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
|
|
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
response =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put("/api/v1/statuses/#{activity.id}", %{
|
|
"status" => "edited",
|
|
"spoiler_text" => "lol"
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert response["content"] == "edited"
|
|
assert response["spoiler_text"] == "lol"
|
|
|
|
response =
|
|
conn
|
|
|> get("/api/v1/statuses/#{activity.id}")
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert response["content"] == "edited"
|
|
assert response["spoiler_text"] == "lol"
|
|
end
|
|
|
|
test "it updates the attachments", %{conn: conn, user: user} do
|
|
attachment = insert(:attachment, user: user)
|
|
attachment_id = to_string(attachment.id)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
|
|
|
|
response =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put("/api/v1/statuses/#{activity.id}", %{
|
|
"status" => "mew mew #abc",
|
|
"spoiler_text" => "#def",
|
|
"media_ids" => [attachment_id]
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert [%{"id" => ^attachment_id}] = response["media_attachments"]
|
|
end
|
|
|
|
test "it does not update visibility", %{conn: conn, user: user} do
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: "mew mew #abc",
|
|
spoiler_text: "#def",
|
|
visibility: "private"
|
|
})
|
|
|
|
response =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put("/api/v1/statuses/#{activity.id}", %{
|
|
"status" => "edited",
|
|
"spoiler_text" => "lol"
|
|
})
|
|
|> json_response_and_validate_schema(200)
|
|
|
|
assert response["visibility"] == "private"
|
|
end
|
|
|
|
test "it refuses to update when original post is not by the user", %{conn: conn} do
|
|
another_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(another_user, %{status: "mew mew #abc", spoiler_text: "#def"})
|
|
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put("/api/v1/statuses/#{activity.id}", %{
|
|
"status" => "edited",
|
|
"spoiler_text" => "lol"
|
|
})
|
|
|> json_response_and_validate_schema(:forbidden)
|
|
end
|
|
|
|
test "it returns 404 if the user cannot see the post", %{conn: conn} do
|
|
another_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(another_user, %{
|
|
status: "mew mew #abc",
|
|
spoiler_text: "#def",
|
|
visibility: "private"
|
|
})
|
|
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> put("/api/v1/statuses/#{activity.id}", %{
|
|
"status" => "edited",
|
|
"spoiler_text" => "lol"
|
|
})
|
|
|> json_response_and_validate_schema(:not_found)
|
|
end
|
|
end
|
|
end
|