0c2b33458d
In Mastodon media can only be used by owners and only be associated with
a single post. We currently allow media to be associated with several
posts and until now did not limit their usage in posts to media owners.
However, media update and GET lookup was already limited to owners.
(In accordance with allowing media reuse, we also still allow GET
lookups of media already used in a post unlike Mastodon)
Allowing reuse isn’t problematic per se, but allowing use by non-owners
can be problematic if media ids of private-scoped posts can be guessed
since creating a new post with this media id will reveal the uploaded
file content and alt text.
Given media ids are currently just part of a sequentieal series shared
with some other objects, guessing media ids is with some persistence
indeed feasible.
E.g. sampline some public media ids from a real-world
instance with 112 total and 61 monthly-active users:
17.465.096 at t0
17.472.673 at t1 = t0 + 4h
17.473.248 at t2 = t1 + 20min
This gives about 30 new ids per minute of which most won't be
local media but remote and local posts, poll answers etc.
Assuming the default ratelimit of 15 post actions per 10s, scraping all
media for the 4h interval takes about 84 minutes and scraping the 20min
range mere 6.3 minutes. (Until the preceding commit, post updates were
not rate limited at all, allowing even faster scraping.)
If an attacker can infer (e.g. via reply to a follower-only post not
accessbile to the attacker) some sensitive information was uploaded
during a specific time interval and has some pointers regarding the
nature of the information, identifying the specific upload out of all
scraped media for this timerange is not impossible.
Thus restrict media usage to owners.
Checking ownership just in ActivitDraft would already be sufficient,
since when a scheduled status actually gets posted it goes through
ActivityDraft again, but would erroneously return a success status
when scheduling an illegal post.
Independently discovered and fixed by mint in Pleroma
1afde067b1
2527 lines
81 KiB
Elixir
2527 lines
81 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_at1 = 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)
|
|
|
|
activity = Activity.get_by_id(fourth_id)
|
|
assert activity
|
|
|
|
{:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
|
assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: fourth_id},
|
|
scheduled_at: expires_at2
|
|
)
|
|
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"])
|
|
|
|
expiry_delay = Timex.diff(expires_at, DateTime.utc_now(), :hours)
|
|
assert(expiry_delay in [23, 24])
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: expires_at
|
|
)
|
|
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 "refuses to post non-owned media", %{conn: conn} do
|
|
other_user = insert(:user)
|
|
|
|
file = %Plug.Upload{
|
|
content_type: "image/jpeg",
|
|
path: Path.absname("test/fixtures/image.jpg"),
|
|
filename: "an_image.jpg"
|
|
}
|
|
|
|
{:ok, upload} = ActivityPub.upload(file, actor: other_user.ap_id)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "mew",
|
|
"media_ids" => [to_string(upload.id)]
|
|
})
|
|
|
|
assert json_response(conn, 422) == %{"error" => "forbidden"}
|
|
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 "refuses to schedule post with non-owned media", %{conn: conn} do
|
|
other_user = insert(:user)
|
|
|
|
file = %Plug.Upload{
|
|
content_type: "image/jpeg",
|
|
path: Path.absname("test/fixtures/image.jpg"),
|
|
filename: "an_image.jpg"
|
|
}
|
|
|
|
{:ok, upload} = ActivityPub.upload(file, actor: other_user.ap_id)
|
|
|
|
conn =
|
|
conn
|
|
|> put_req_header("content-type", "application/json")
|
|
|> post("/api/v1/statuses", %{
|
|
"status" => "mew",
|
|
"scheduled_at" => DateTime.add(DateTime.utc_now(), 6, :minute),
|
|
"media_ids" => [to_string(upload.id)]
|
|
})
|
|
|
|
assert json_response(conn, 403) == %{"error" => "Access denied"}
|
|
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_at1 = 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)
|
|
|
|
activity = Activity.get_by_id(id)
|
|
{:ok, expires_at2, _} = DateTime.from_iso8601(activity.data["expires_at"])
|
|
|
|
assert Timex.compare(expires_at1, expires_at2, :minutes) == 0
|
|
|
|
assert_enqueued(
|
|
worker: Pleroma.Workers.PurgeExpiredActivity,
|
|
args: %{activity_id: id},
|
|
scheduled_at: expires_at2
|
|
)
|
|
|
|
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_at2
|
|
)
|
|
|
|
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_at2
|
|
)
|
|
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 Timex.compare(expires_at, a_expires_at, :minutes) == 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 to non-owned attachments", %{conn: conn, user: user} do
|
|
other_user = insert(:user)
|
|
attachment = insert(:attachment, user: other_user)
|
|
attachment_id = to_string(attachment.id)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "mew mew #abc", spoiler_text: "#def"})
|
|
|
|
conn =
|
|
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]
|
|
})
|
|
|
|
assert json_response(conn, 400) == %{"error" => "internal_server_error"}
|
|
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
|