use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
- import Pleroma.Factory
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Delivery
alias Pleroma.Object
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Endpoint
alias Pleroma.Workers.ReceiverWorker
+ import Pleroma.Factory
+
+ require Pleroma.Constants
+
setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
end
+ describe "mastodon compatibility routes" do
+ test "it returns a json representation of the object with accept application/json", %{
+ conn: conn
+ } do
+ {:ok, object} =
+ %{
+ "type" => "Note",
+ "content" => "hey",
+ "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
+ "actor" => Endpoint.url() <> "/users/raymoo",
+ "to" => [Pleroma.Constants.as_public()]
+ }
+ |> Object.create()
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/raymoo/statuses/999999999")
+
+ assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
+ end
+
+ test "it returns a json representation of the activity with accept application/json", %{
+ conn: conn
+ } do
+ {:ok, object} =
+ %{
+ "type" => "Note",
+ "content" => "hey",
+ "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
+ "actor" => Endpoint.url() <> "/users/raymoo",
+ "to" => [Pleroma.Constants.as_public()]
+ }
+ |> Object.create()
+
+ {:ok, activity, _} =
+ %{
+ "id" => object.data["id"] <> "/activity",
+ "type" => "Create",
+ "object" => object.data["id"],
+ "actor" => object.data["actor"],
+ "to" => object.data["to"]
+ }
+ |> ActivityPub.persist(local: true)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/users/raymoo/statuses/999999999/activity")
+
+ assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
+ end
+ end
+
describe "/objects/:uuid" do
test "it returns a json representation of the object with accept application/json", %{
conn: conn
test "cached purged after activity deletion", %{conn: conn} do
user = insert(:user)
- {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"})
+ {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
uuid = String.split(activity.data["id"], "/") |> List.last()
assert Activity.get_by_ap_id(data["id"])
end
+ @tag capture_log: true
+ test "it inserts an incoming activity into the database" <>
+ "even if we can't fetch the user but have it in our db",
+ %{conn: conn} do
+ user =
+ insert(:user,
+ ap_id: "https://mastodon.example.org/users/raymoo",
+ ap_enabled: true,
+ local: false,
+ last_refreshed_at: nil
+ )
+
+ data =
+ File.read!("test/fixtures/mastodon-post-activity.json")
+ |> Poison.decode!()
+ |> Map.put("actor", user.ap_id)
+ |> put_in(["object", "attridbutedTo"], user.ap_id)
+
+ conn =
+ conn
+ |> assign(:valid_signature, true)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/inbox", data)
+
+ assert "ok" == json_response(conn, 200)
+
+ ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+ assert Activity.get_by_ap_id(data["id"])
+ end
+
test "it clears `unreachable` federation status of the sender", %{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
end
+ @tag capture_log: true
test "without valid signature, " <>
"it only accepts Create activities and requires enabled federation",
%{conn: conn} do
test "it accepts announces with to as string instead of array", %{conn: conn} do
user = insert(:user)
+ {:ok, post} = CommonAPI.post(user, %{status: "hey"})
+ announcer = insert(:user, local: false)
+
data = %{
"@context" => "https://www.w3.org/ns/activitystreams",
- "actor" => "http://mastodon.example.org/users/admin",
- "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
- "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
+ "actor" => announcer.ap_id,
+ "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
+ "object" => post.data["object"],
"to" => "https://www.w3.org/ns/activitystreams#Public",
"cc" => [user.ap_id],
"type" => "Announce"
end
describe "GET /users/:nickname/outbox" do
+ test "it paginates correctly", %{conn: conn} do
+ user = insert(:user)
+ conn = assign(conn, :user, user)
+ outbox_endpoint = user.ap_id <> "/outbox"
+
+ _posts =
+ for i <- 0..25 do
+ {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
+ activity
+ end
+
+ result =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(outbox_endpoint <> "?page=true")
+ |> json_response(200)
+
+ result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
+ assert length(result["orderedItems"]) == 20
+ assert length(result_ids) == 20
+ assert result["next"]
+ assert String.starts_with?(result["next"], outbox_endpoint)
+
+ result_next =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(result["next"])
+ |> json_response(200)
+
+ result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
+ assert length(result_next["orderedItems"]) == 6
+ assert length(result_next_ids) == 6
+ refute Enum.find(result_next_ids, fn x -> x in result_ids end)
+ refute Enum.find(result_ids, fn x -> x in result_next_ids end)
+ assert String.starts_with?(result["id"], outbox_endpoint)
+
+ result_next_again =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(result_next["id"])
+ |> json_response(200)
+
+ assert result_next == result_next_again
+ end
+
test "it returns 200 even if there're no activities", %{conn: conn} do
user = insert(:user)
+ outbox_endpoint = user.ap_id <> "/outbox"
conn =
conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
- |> get("/users/#{user.nickname}/outbox")
+ |> get(outbox_endpoint)
result = json_response(conn, 200)
- assert user.ap_id <> "/outbox" == result["id"]
+ assert outbox_endpoint == result["id"]
end
test "it returns a note activity in a collection", %{conn: conn} do
end
end
- describe "POST /users/:nickname/outbox" do
- test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do
- data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ describe "POST /users/:nickname/outbox (C2S)" do
+ setup do
+ [
+ activity: %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "object" => %{"type" => "Note", "content" => "AP C2S test"},
+ "to" => "https://www.w3.org/ns/activitystreams#Public",
+ "cc" => []
+ }
+ ]
+ end
+
+ test "it rejects posts from other users / unauthenticated users", %{
+ conn: conn,
+ activity: activity
+ } do
user = insert(:user)
other_user = insert(:user)
conn = put_req_header(conn, "content-type", "application/activity+json")
conn
- |> post("/users/#{user.nickname}/outbox", data)
+ |> post("/users/#{user.nickname}/outbox", activity)
|> json_response(403)
conn
|> assign(:user, other_user)
- |> post("/users/#{user.nickname}/outbox", data)
+ |> post("/users/#{user.nickname}/outbox", activity)
|> json_response(403)
end
- test "it inserts an incoming create activity into the database", %{conn: conn} do
- data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ test "it inserts an incoming create activity into the database", %{
+ conn: conn,
+ activity: activity
+ } do
user = insert(:user)
- conn =
+ result =
conn
|> assign(:user, user)
|> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
-
- result = json_response(conn, 201)
+ |> post("/users/#{user.nickname}/outbox", activity)
+ |> json_response(201)
assert Activity.get_by_ap_id(result["id"])
+ assert result["object"]
+ assert %Object{data: object} = Object.normalize(result["object"])
+ assert object["content"] == activity["object"]["content"]
end
- test "it rejects an incoming activity with bogus type", %{conn: conn} do
- data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
+ test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
user = insert(:user)
- data =
- data
- |> Map.put("type", "BadType")
+ activity =
+ activity
+ |> put_in(["object", "type"], "Benis")
+
+ _result =
+ conn
+ |> assign(:user, user)
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", activity)
+ |> json_response(400)
+ end
+
+ test "it inserts an incoming sensitive activity into the database", %{
+ conn: conn,
+ activity: activity
+ } do
+ user = insert(:user)
+ conn = assign(conn, :user, user)
+ object = Map.put(activity["object"], "sensitive", true)
+ activity = Map.put(activity, "object", object)
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/activity+json")
+ |> post("/users/#{user.nickname}/outbox", activity)
+ |> json_response(201)
+
+ assert Activity.get_by_ap_id(response["id"])
+ assert response["object"]
+ assert %Object{data: response_object} = Object.normalize(response["object"])
+ assert response_object["sensitive"] == true
+ assert response_object["content"] == activity["object"]["content"]
+
+ representation =
+ conn
+ |> put_req_header("accept", "application/activity+json")
+ |> get(response["id"])
+ |> json_response(200)
+
+ assert representation["object"]["sensitive"] == true
+ end
+
+ test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
+ user = insert(:user)
+ activity = Map.put(activity, "type", "BadType")
conn =
conn
|> assign(:user, user)
|> put_req_header("content-type", "application/activity+json")
- |> post("/users/#{user.nickname}/outbox", data)
+ |> post("/users/#{user.nickname}/outbox", activity)
assert json_response(conn, 400)
end
assert result["totalItems"] == 15
end
- test "returns 403 if requester is not logged in", %{conn: conn} do
+ test "does not require authentication", %{conn: conn} do
user = insert(:user)
conn
|> get("/users/#{user.nickname}/followers")
- |> json_response(403)
+ |> json_response(200)
end
end
assert result["totalItems"] == 15
end
- test "returns 403 if requester is not logged in", %{conn: conn} do
+ test "does not require authentication", %{conn: conn} do
user = insert(:user)
conn
|> get("/users/#{user.nickname}/following")
- |> json_response(403)
+ |> json_response(200)
end
end