added tests for OstatusController

This commit is contained in:
Maksim 2019-07-29 05:02:20 +00:00 committed by kaniini
parent b38d21060e
commit 159bbec570
2 changed files with 585 additions and 215 deletions

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Web.OStatus.OStatusController do defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Fallback.RedirectController
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
@ -12,42 +13,44 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Router
alias Pleroma.Web.XML alias Pleroma.Web.XML
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
plug(
Pleroma.Plugs.SetFormatPlug
when action in [:feed_redirect, :object, :activity, :notice]
)
action_fallback(:errors) action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <-
{:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do def feed_redirect(conn, %{"nickname" => nickname}) do
case get_format(conn) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
"html" ->
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
else
nil -> {:error, :not_found}
end
"activity+json" ->
ActivityPubController.call(conn, :user)
"json" ->
ActivityPubController.call(conn, :user)
_ ->
with %User{} = user <- User.get_cached_by_nickname(nickname) do
redirect(conn, external: OStatus.feed_path(user)) redirect(conn, external: OStatus.feed_path(user))
else
nil -> {:error, :not_found}
end
end end
end end
def feed(conn, %{"nickname" => nickname} = params) do def feed(conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
query_params = query_params =
Map.take(params, ["max_id"]) Map.take(params, ["max_id"])
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
@ -65,8 +68,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
conn conn
|> put_resp_content_type("application/atom+xml") |> put_resp_content_type("application/atom+xml")
|> send_resp(200, response) |> send_resp(200, response)
else
nil -> {:error, :not_found}
end end
end end
@ -97,93 +98,82 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|> send_resp(200, "") |> send_resp(200, "")
end end
def object(conn, %{"uuid" => uuid}) do def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
if get_format(conn) in ["activity+json", "json"] do when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :object) ActivityPubController.call(conn, :object)
else end
def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
with id <- o_status_url(conn, :object, uuid), with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <- {_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)}, {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, nil, activity, user) _ -> represent_activity(conn, nil, activity, user)
end end
else else
{:public?, false} -> reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
{:activity, nil} ->
{:error, :not_found} {:error, :not_found}
e -> e ->
e e
end end
end end
def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :activity)
end end
def activity(conn, %{"uuid" => uuid}) do def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
if get_format(conn) in ["activity+json", "json"] do
ActivityPubController.call(conn, :activity)
else
with id <- o_status_url(conn, :activity, uuid), with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}") "html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, format, activity, user) _ -> represent_activity(conn, format, activity, user)
end end
else else
{:public?, false} -> reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
{:activity, nil} ->
{:error, :not_found} {:error, :not_found}
e -> e ->
e e
end end
end end
end
def notice(conn, %{"id" => id}) do def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)}, {_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case format = get_format(conn) do cond do
"html" -> format == "html" && activity.data["type"] == "Create" ->
if activity.data["type"] == "Create" do
%Object{} = object = Object.normalize(activity) %Object{} = object = Object.normalize(activity)
Fallback.RedirectController.redirector_with_meta(conn, %{ RedirectController.redirector_with_meta(
conn,
%{
activity_id: activity.id, activity_id: activity.id,
object: object, object: object,
url: url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
Pleroma.Web.Router.Helpers.o_status_url(
Pleroma.Web.Endpoint,
:notice,
activity.id
),
user: user user: user
}) }
else )
Fallback.RedirectController.redirector(conn, nil)
end
_ -> format == "html" ->
RedirectController.redirector(conn, nil)
true ->
represent_activity(conn, format, activity, user) represent_activity(conn, format, activity, user)
end end
else else
{:public?, false} -> reason when reason in [{:public?, false}, {:activity, nil}] ->
conn conn
|> put_status(404) |> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404) |> RedirectController.redirector(nil, 404)
{:activity, nil} ->
conn
|> Fallback.RedirectController.redirector(nil, 404)
e -> e ->
e e
@ -204,13 +194,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
"content-security-policy", "content-security-policy",
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
) )
|> put_view(Pleroma.Web.Metadata.PlayerView) |> put_view(PlayerView)
|> render("player.html", url) |> render("player.html", url)
else else
_error -> _error ->
conn conn
|> put_status(404) |> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404) |> RedirectController.redirector(nil, 404)
end end
end end
@ -248,6 +238,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do
render_error(conn, :not_found, "Not found") render_error(conn, :not_found, "Not found")
end end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong") render_error(conn, :internal_server_error, "Something went wrong")
end end

View File

@ -101,6 +101,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 404) assert response(conn, 404)
end end
describe "GET object/2" do
test "gets an object", %{conn: conn} do test "gets an object", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
@ -122,6 +123,37 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 200) == expected assert response(conn, 200) == expected
end end
test "redirects to /notice/id for html format", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "text/html")
|> get(url)
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "500s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/xml")
|> get(url)
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on private objects", %{conn: conn} do test "404s on private objects", %{conn: conn} do
note_activity = insert(:direct_note_activity) note_activity = insert(:direct_note_activity)
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
@ -137,7 +169,9 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> get("/objects/123") |> get("/objects/123")
|> response(404) |> response(404)
end end
end
describe "GET activity/2" do
test "gets an activity in xml format", %{conn: conn} do test "gets an activity in xml format", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
@ -148,6 +182,33 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> response(200) |> response(200)
end end
test "redirects to /notice/id for html format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
conn =
conn
|> put_req_header("accept", "text/html")
|> get("/activities/#{uuid}")
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "505s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
conn =
conn
|> put_req_header("accept", "text/html")
|> get("/activities/#{uuid}")
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on deleted objects", %{conn: conn} do test "404s on deleted objects", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
object = Object.normalize(note_activity) object = Object.normalize(note_activity)
@ -181,6 +242,21 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> response(404) |> response(404)
end end
test "gets an activity in AS2 format", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
url = "/activities/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get(url)
assert json_response(conn, 200)
end
end
describe "GET notice/2" do
test "gets a notice in xml format", %{conn: conn} do test "gets a notice in xml format", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
@ -198,6 +274,19 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|> json_response(200) |> json_response(200)
end end
test "500s when actor not found", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
conn =
conn
|> get("/notice/#{note_activity.id}")
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
url = "/notice/#{note_activity.id}" url = "/notice/#{note_activity.id}"
@ -224,17 +313,31 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 404) assert response(conn, 404)
end end
test "gets an activity in AS2 format", %{conn: conn} do test "render html for redirect for html format", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
url = "/activities/#{uuid}"
conn = resp =
conn conn
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "text/html")
|> get(url) |> get("/notice/#{note_activity.id}")
|> response(200)
assert json_response(conn, 200) assert resp =~
"<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">"
user = insert(:user)
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
assert like_activity.data["type"] == "Like"
resp =
conn
|> put_req_header("accept", "text/html")
|> get("/notice/#{like_activity.id}")
|> response(200)
assert resp =~ "<!--server-generated-meta-->"
end end
test "404s a private notice", %{conn: conn} do test "404s a private notice", %{conn: conn} do
@ -257,4 +360,279 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 404) assert response(conn, 404)
end end
end
describe "feed_redirect" do
test "undefined format. it redirects to feed", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> put_req_header("accept", "application/xml")
|> get("/users/#{user.nickname}")
|> response(302)
assert response ==
"<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{
user.nickname
}/feed.atom\">redirected</a>.</body></html>"
end
test "undefined format. it returns error when user not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/xml")
|> get("/users/jimm")
|> response(404)
assert response == ~S({"error":"Not found"})
end
test "activity+json format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}")
|> json_response(200)
assert response["endpoints"] == %{
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
}
assert response["@context"] == [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{"@language" => "und"}
]
assert Map.take(response, [
"followers",
"following",
"id",
"inbox",
"manuallyApprovesFollowers",
"name",
"outbox",
"preferredUsername",
"summary",
"tag",
"type",
"url"
]) == %{
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
"manuallyApprovesFollowers" => false,
"name" => user.name,
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
"preferredUsername" => user.nickname,
"summary" => user.bio,
"tag" => [],
"type" => "Person",
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
}
end
test "activity+json format. it returns error whe use not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/users/jimm")
|> json_response(404)
assert response == "Not found"
end
test "json format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> put_req_header("accept", "application/json")
|> get("/users/#{user.nickname}")
|> json_response(200)
assert response["endpoints"] == %{
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
}
assert response["@context"] == [
"https://www.w3.org/ns/activitystreams",
"http://localhost:4001/schemas/litepub-0.1.jsonld",
%{"@language" => "und"}
]
assert Map.take(response, [
"followers",
"following",
"id",
"inbox",
"manuallyApprovesFollowers",
"name",
"outbox",
"preferredUsername",
"summary",
"tag",
"type",
"url"
]) == %{
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
"manuallyApprovesFollowers" => false,
"name" => user.name,
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
"preferredUsername" => user.nickname,
"summary" => user.bio,
"tag" => [],
"type" => "Person",
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
}
end
test "json format. it returns error whe use not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/json")
|> get("/users/jimm")
|> json_response(404)
assert response == "Not found"
end
test "html format. it redirects on actual feed of user", %{conn: conn} do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
response =
conn
|> get("/users/#{user.nickname}")
|> response(200)
assert response ==
Fallback.RedirectController.redirector_with_meta(
conn,
%{user: user}
).resp_body
end
test "html format. it returns error when user not found", %{conn: conn} do
response =
conn
|> get("/users/jimm")
|> json_response(404)
assert response == %{"error" => "Not found"}
end
end
describe "GET /notice/:id/embed_player" do
test "render embed player", %{conn: conn} do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
object_data =
Map.put(object.data, "attachment", [
%{
"url" => [
%{
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
conn =
conn
|> get("/notice/#{note_activity.id}/embed_player")
assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"]
assert Plug.Conn.get_resp_header(
conn,
"content-security-policy"
) == [
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
]
assert response(conn, 200) =~
"<video controls loop><source src=\"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4\" type=\"video/mp4\">Your browser does not support video/mp4 playback.</video>"
end
test "404s when activity isn't create", %{conn: conn} do
note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"})
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when activity is direct message", %{conn: conn} do
note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true})
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when attachment is empty", %{conn: conn} do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
object_data = Map.put(object.data, "attachment", [])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
test "404s when attachment isn't audio or video", %{conn: conn} do
note_activity = insert(:note_activity)
object = Pleroma.Object.normalize(note_activity)
object_data =
Map.put(object.data, "attachment", [
%{
"url" => [
%{
"href" => "https://peertube.moe/static/webseed/480.jpg",
"mediaType" => "image/jpg",
"type" => "Link"
}
]
}
])
object
|> Ecto.Changeset.change(data: object_data)
|> Pleroma.Repo.update()
assert conn
|> get("/notice/#{note_activity.id}/embed_player")
|> response(404)
end
end
end end