From df469b4468168cf072e73df73e0fdde2bbab1da5 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 11 Dec 2019 12:52:57 -0600 Subject: [PATCH 01/38] Benchmark env uses test database so we should be able to use test.secret.exs --- config/benchmark.exs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/benchmark.exs b/config/benchmark.exs index dd99cf5fd..c7ddb80e7 100644 --- a/config/benchmark.exs +++ b/config/benchmark.exs @@ -82,3 +82,11 @@ IO.puts("RUM enabled: #{rum_enabled}") config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock + +if File.exists?("./config/test.secret.exs") do + import_config "test.secret.exs" +else + IO.puts( + "You may want to create test.secret.exs to declare custom database connection parameters." + ) +end From 7973cbdb9fa9120306cb5a265a477eeccd315ee6 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Sun, 15 Dec 2019 22:32:42 +0300 Subject: [PATCH 02/38] OAuthScopesPlug: disallowed nil token (unless with :fallback option). WIP: controller tests modification: OAuth scopes usage. --- lib/pleroma/plugs/oauth_scopes_plug.ex | 9 +- lib/pleroma/user.ex | 6 +- .../controllers/emoji_api_controller.ex | 2 +- .../controllers/pleroma_api_controller.ex | 9 +- .../controllers/util_controller.ex | 57 +- test/notification_test.exs | 2 +- test/plugs/oauth_scopes_plug_test.exs | 157 ++--- test/support/conn_case.ex | 20 + test/support/factory.ex | 32 +- .../admin_api/admin_api_controller_test.exs | 566 +++++++----------- .../controllers/status_controller_test.exs | 392 +++++------- test/web/oauth/oauth_controller_test.exs | 10 +- .../pleroma_api_controller_test.exs | 40 +- test/web/twitter_api/util_controller_test.exs | 248 ++++---- 14 files changed, 638 insertions(+), 912 deletions(-) diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index 174a8389c..07c0f7fdb 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -18,16 +18,13 @@ def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do token = assigns[:token] scopes = transform_scopes(scopes, options) - matched_scopes = token && filter_descendants(scopes, token.scopes) + matched_scopes = (token && filter_descendants(scopes, token.scopes)) || [] cond do - is_nil(token) -> - maybe_perform_instance_privacy_check(conn, options) - - op == :| && Enum.any?(matched_scopes) -> + token && op == :| && Enum.any?(matched_scopes) -> conn - op == :& && matched_scopes == scopes -> + token && op == :& && matched_scopes == scopes -> conn options[:fallback] == :proceed_unauthenticated -> diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 706aee2ff..021a542b3 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1855,9 +1855,9 @@ def admin_api_update(user, params) do ]) with {:ok, updated_user} <- update_and_set_cache(changeset) do - if user.is_admin && !updated_user.is_admin do - # Tokens & authorizations containing any admin scopes must be revoked (revoking all). - # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins). + if user.is_admin != updated_user.is_admin do + # Admin status change results in change of accessible OAuth scopes, and instead of changing + # already issued tokens we revoke them, requiring user to sign in again global_sign_out(user) end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 69dfa92e3..0bbf84fd3 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -52,7 +52,7 @@ def list_from(conn, %{"instance_address" => address}) do @doc """ Lists the packs available on the instance as JSON. - The information is public and does not require authentification. The format is + The information is public and does not require authentication. The format is a map of "pack directory name" to pack.json contents. """ def list_packs(conn, _params) do diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 8fed3f5bb..772c535a4 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -22,7 +22,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do plug( OAuthScopesPlug, - %{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses] + %{scopes: ["read:statuses"]} + when action in [:conversation, :conversation_statuses, :emoji_reactions_by] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:statuses"]} + when action in [:react_with_emoji, :unreact_with_emoji] ) plug( diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 2305bb413..849783d4a 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -22,7 +22,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do plug( OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} - when action in [:do_remote_follow, :follow_import] + when action == :follow_import + ) + + # Note: follower can submit the form (with password auth) not being signed in (having no token) + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} + when action == :do_remote_follow ) plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) @@ -112,6 +119,28 @@ defp is_status?(acct) do end end + def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) + when not is_nil(user) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do + conn + |> render("followed.html", %{error: false}) + else + # Was already following user + {:error, "Could not follow user:" <> _rest} -> + render(conn, "followed.html", %{error: "Error following account"}) + + {:fetch_user, error} -> + Logger.debug("Remote follow failed with error #{inspect(error)}") + render(conn, "followed.html", %{error: "Could not find user"}) + + e -> + Logger.debug("Remote follow failed with error #{inspect(e)}") + render(conn, "followed.html", %{error: "Something went wrong."}) + end + end + + # Note: "id" is the id of followee user, disregard incorrect placing under "authorization" def do_remote_follow(conn, %{ "authorization" => %{"name" => username, "password" => password, "id" => id} }) do @@ -145,24 +174,12 @@ def do_remote_follow(conn, %{ end end - def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, - {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do - conn - |> render("followed.html", %{error: false}) - else - # Was already following user - {:error, "Could not follow user:" <> _rest} -> - render(conn, "followed.html", %{error: "Error following account"}) + def do_remote_follow(%{assigns: %{user: nil}} = conn, _) do + render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."}) + end - {:fetch_user, error} -> - Logger.debug("Remote follow failed with error #{inspect(error)}") - render(conn, "followed.html", %{error: "Could not find user"}) - - e -> - Logger.debug("Remote follow failed with error #{inspect(e)}") - render(conn, "followed.html", %{error: "Something went wrong."}) - end + def do_remote_follow(conn, _) do + render(conn, "followed.html", %{error: "Something went wrong."}) end def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do @@ -345,7 +362,9 @@ def change_email(%{assigns: %{user: user}} = conn, params) do end def delete_account(%{assigns: %{user: user}} = conn, params) do - case CommonAPI.Utils.confirm_current_password(user, params["password"]) do + password = params["password"] || "" + + case CommonAPI.Utils.confirm_current_password(user, password) do {:ok, user} -> User.delete(user) json(conn, %{status: "success"}) diff --git a/test/notification_test.exs b/test/notification_test.exs index ffa3d4b8c..f5f23bb5a 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -98,7 +98,7 @@ test "it creates a notification for user if the user blocks the activity author" assert Notification.create_notification(activity, user) end - test "it creates a notificatin for the user if the user mutes the activity author" do + test "it creates a notification for the user if the user mutes the activity author" do muter = insert(:user) muted = insert(:user) {:ok, _} = User.mute(muter, muted) diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/plugs/oauth_scopes_plug_test.exs index 89f32f43a..ce426677b 100644 --- a/test/plugs/oauth_scopes_plug_test.exs +++ b/test/plugs/oauth_scopes_plug_test.exs @@ -16,34 +16,6 @@ defmodule Pleroma.Plugs.OAuthScopesPlugTest do :ok end - describe "when `assigns[:token]` is nil, " do - test "with :skip_instance_privacy_check option, proceeds with no op", %{conn: conn} do - conn = - conn - |> assign(:user, insert(:user)) - |> OAuthScopesPlug.call(%{scopes: ["read"], skip_instance_privacy_check: true}) - - refute conn.halted - assert conn.assigns[:user] - - refute called(EnsurePublicOrAuthenticatedPlug.call(conn, :_)) - end - - test "without :skip_instance_privacy_check option, calls EnsurePublicOrAuthenticatedPlug", %{ - conn: conn - } do - conn = - conn - |> assign(:user, insert(:user)) - |> OAuthScopesPlug.call(%{scopes: ["read"]}) - - refute conn.halted - assert conn.assigns[:user] - - assert called(EnsurePublicOrAuthenticatedPlug.call(conn, :_)) - end - end - test "if `token.scopes` fulfills specified 'any of' conditions, " <> "proceeds with no op", %{conn: conn} do @@ -75,64 +47,56 @@ test "if `token.scopes` fulfills specified 'all of' conditions, " <> end describe "with `fallback: :proceed_unauthenticated` option, " do - test "if `token.scopes` doesn't fulfill specified 'any of' conditions, " <> - "clears `assigns[:user]` and calls EnsurePublicOrAuthenticatedPlug", + test "if `token.scopes` doesn't fulfill specified conditions, " <> + "clears :user and :token assigns and calls EnsurePublicOrAuthenticatedPlug", %{conn: conn} do - token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user) + user = insert(:user) + token1 = insert(:oauth_token, scopes: ["read", "write"], user: user) - conn = - conn - |> assign(:user, token.user) - |> assign(:token, token) - |> OAuthScopesPlug.call(%{scopes: ["follow"], fallback: :proceed_unauthenticated}) + for token <- [token1, nil], op <- [:|, :&] do + ret_conn = + conn + |> assign(:user, user) + |> assign(:token, token) + |> OAuthScopesPlug.call(%{ + scopes: ["follow"], + op: op, + fallback: :proceed_unauthenticated + }) - refute conn.halted - refute conn.assigns[:user] + refute ret_conn.halted + refute ret_conn.assigns[:user] + refute ret_conn.assigns[:token] - assert called(EnsurePublicOrAuthenticatedPlug.call(conn, :_)) - end - - test "if `token.scopes` doesn't fulfill specified 'all of' conditions, " <> - "clears `assigns[:user] and calls EnsurePublicOrAuthenticatedPlug", - %{conn: conn} do - token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user) - - conn = - conn - |> assign(:user, token.user) - |> assign(:token, token) - |> OAuthScopesPlug.call(%{ - scopes: ["read", "follow"], - op: :&, - fallback: :proceed_unauthenticated - }) - - refute conn.halted - refute conn.assigns[:user] - - assert called(EnsurePublicOrAuthenticatedPlug.call(conn, :_)) + assert called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_)) + end end test "with :skip_instance_privacy_check option, " <> "if `token.scopes` doesn't fulfill specified conditions, " <> - "clears `assigns[:user]` and does not call EnsurePublicOrAuthenticatedPlug", + "clears :user and :token assigns and does NOT call EnsurePublicOrAuthenticatedPlug", %{conn: conn} do - token = insert(:oauth_token, scopes: ["read:statuses", "write"]) |> Repo.preload(:user) + user = insert(:user) + token1 = insert(:oauth_token, scopes: ["read:statuses", "write"], user: user) - conn = - conn - |> assign(:user, token.user) - |> assign(:token, token) - |> OAuthScopesPlug.call(%{ - scopes: ["read"], - fallback: :proceed_unauthenticated, - skip_instance_privacy_check: true - }) + for token <- [token1, nil], op <- [:|, :&] do + ret_conn = + conn + |> assign(:user, user) + |> assign(:token, token) + |> OAuthScopesPlug.call(%{ + scopes: ["read"], + op: op, + fallback: :proceed_unauthenticated, + skip_instance_privacy_check: true + }) - refute conn.halted - refute conn.assigns[:user] + refute ret_conn.halted + refute ret_conn.assigns[:user] + refute ret_conn.assigns[:token] - refute called(EnsurePublicOrAuthenticatedPlug.call(conn, :_)) + refute called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_)) + end end end @@ -140,39 +104,42 @@ test "with :skip_instance_privacy_check option, " <> test "if `token.scopes` does not fulfill specified 'any of' conditions, " <> "returns 403 and halts", %{conn: conn} do - token = insert(:oauth_token, scopes: ["read", "write"]) - any_of_scopes = ["follow"] + for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do + any_of_scopes = ["follow", "push"] - conn = - conn - |> assign(:token, token) - |> OAuthScopesPlug.call(%{scopes: any_of_scopes}) + ret_conn = + conn + |> assign(:token, token) + |> OAuthScopesPlug.call(%{scopes: any_of_scopes}) - assert conn.halted - assert 403 == conn.status + assert ret_conn.halted + assert 403 == ret_conn.status - expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, ", ")}." - assert Jason.encode!(%{error: expected_error}) == conn.resp_body + expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, " | ")}." + assert Jason.encode!(%{error: expected_error}) == ret_conn.resp_body + end end test "if `token.scopes` does not fulfill specified 'all of' conditions, " <> "returns 403 and halts", %{conn: conn} do - token = insert(:oauth_token, scopes: ["read", "write"]) - all_of_scopes = ["write", "follow"] + for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do + token_scopes = (token && token.scopes) || [] + all_of_scopes = ["write", "follow"] - conn = - conn - |> assign(:token, token) - |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&}) + conn = + conn + |> assign(:token, token) + |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&}) - assert conn.halted - assert 403 == conn.status + assert conn.halted + assert 403 == conn.status - expected_error = - "Insufficient permissions: #{Enum.join(all_of_scopes -- token.scopes, ", ")}." + expected_error = + "Insufficient permissions: #{Enum.join(all_of_scopes -- token_scopes, " & ")}." - assert Jason.encode!(%{error: expected_error}) == conn.resp_body + assert Jason.encode!(%{error: expected_error}) == conn.resp_body + end end end diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 9897f72ce..95bc2492a 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -28,6 +28,26 @@ defmodule Pleroma.Web.ConnCase do # The default endpoint for testing @endpoint Pleroma.Web.Endpoint + + # Sets up OAuth access with specified scopes + defp oauth_access(scopes, opts \\ %{}) do + user = + Map.get_lazy(opts, :user, fn -> + Pleroma.Factory.insert(:user) + end) + + token = + Map.get_lazy(opts, :oauth_token, fn -> + Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes) + end) + + conn = + build_conn() + |> assign(:user, user) + |> assign(:token, token) + + %{user: user, token: token, conn: conn} + end end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 314f26ec9..100864055 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -296,7 +296,7 @@ def oauth_app_factory do %Pleroma.Web.OAuth.App{ client_name: "Some client", redirect_uris: "https://example.com/callback", - scopes: ["read", "write", "follow", "push"], + scopes: ["read", "write", "follow", "push", "admin"], website: "https://example.com", client_id: Ecto.UUID.generate(), client_secret: "aaa;/&bbb" @@ -310,19 +310,37 @@ def instance_factory do } end - def oauth_token_factory do - oauth_app = insert(:oauth_app) + def oauth_token_factory(attrs \\ %{}) do + scopes = Map.get(attrs, :scopes, ["read"]) + oauth_app = Map.get_lazy(attrs, :app, fn -> insert(:oauth_app, scopes: scopes) end) + user = Map.get_lazy(attrs, :user, fn -> build(:user) end) + + valid_until = + Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) %Pleroma.Web.OAuth.Token{ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), - scopes: ["read"], refresh_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), - user: build(:user), - app_id: oauth_app.id, - valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10) + scopes: scopes, + user: user, + app: oauth_app, + valid_until: valid_until } end + def oauth_admin_token_factory(attrs \\ %{}) do + user = Map.get_lazy(attrs, :user, fn -> build(:user, is_admin: true) end) + + scopes = + attrs + |> Map.get(:scopes, ["admin"]) + |> Kernel.++(["admin"]) + |> Enum.uniq() + + attrs = Map.merge(attrs, %{user: user, scopes: scopes}) + oauth_token_factory(attrs) + end + def oauth_authorization_factory do %Pleroma.Web.OAuth.Authorization{ token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false), diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 49ff005b6..a3fbb6041 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -26,8 +26,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do :ok end - clear_config([:auth, :enforce_oauth_admin_scope_usage]) do - Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} end describe "with [:auth, :enforce_oauth_admin_scope_usage]," do @@ -35,9 +43,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true) end - test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope" do + test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope", + %{admin: admin} do user = insert(:user) - admin = insert(:user, is_admin: true) url = "/api/pleroma/admin/users/#{user.nickname}" good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) @@ -80,14 +88,67 @@ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or bro end end + describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do + clear_config([:auth, :enforce_oauth_admin_scope_usage]) do + Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) + end + + test "GET /api/pleroma/admin/users/:nickname requires " <> + "read:accounts or admin:read:accounts or broader scope", + %{admin: admin} do + user = insert(:user) + url = "/api/pleroma/admin/users/#{user.nickname}" + + good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) + good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) + good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) + good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) + good_token5 = insert(:oauth_token, user: admin, scopes: ["read"]) + + good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5] + + bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"]) + bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) + bad_token3 = nil + + for good_token <- good_tokens do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, 200) + end + + for good_token <- good_tokens do + conn = + build_conn() + |> assign(:user, nil) + |> assign(:token, good_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + + for bad_token <- [bad_token1, bad_token2, bad_token3] do + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, bad_token) + |> get(url) + + assert json_response(conn, :forbidden) + end + end + end + describe "DELETE /api/pleroma/admin/users" do - test "single user" do - admin = insert(:user, is_admin: true) + test "single user", %{admin: admin, conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") @@ -99,14 +160,12 @@ test "single user" do assert json_response(conn, 200) == user.nickname end - test "multiple users" do - admin = insert(:user, is_admin: true) + test "multiple users", %{admin: admin, conn: conn} do user_one = insert(:user) user_two = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete("/api/pleroma/admin/users", %{ nicknames: [user_one.nickname, user_two.nickname] @@ -123,12 +182,9 @@ test "multiple users" do end describe "/api/pleroma/admin/users" do - test "Create" do - admin = insert(:user, is_admin: true) - + test "Create", %{conn: conn} do conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -153,13 +209,11 @@ test "Create" do assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == [] end - test "Cannot create user with existing email" do - admin = insert(:user, is_admin: true) + test "Cannot create user with existing email", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -184,13 +238,11 @@ test "Cannot create user with existing email" do ] end - test "Cannot create user with existing nickname" do - admin = insert(:user, is_admin: true) + test "Cannot create user with existing nickname", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -215,13 +267,11 @@ test "Cannot create user with existing nickname" do ] end - test "Multiple user creation works in transaction" do - admin = insert(:user, is_admin: true) + test "Multiple user creation works in transaction", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users", %{ "users" => [ @@ -265,13 +315,9 @@ test "Multiple user creation works in transaction" do describe "/api/pleroma/admin/users/:nickname" do test "Show", %{conn: conn} do - admin = insert(:user, is_admin: true) user = insert(:user) - conn = - conn - |> assign(:user, admin) - |> get("/api/pleroma/admin/users/#{user.nickname}") + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") expected = %{ "deactivated" => false, @@ -289,26 +335,20 @@ test "Show", %{conn: conn} do end test "when the user doesn't exist", %{conn: conn} do - admin = insert(:user, is_admin: true) user = build(:user) - conn = - conn - |> assign(:user, admin) - |> get("/api/pleroma/admin/users/#{user.nickname}") + conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") assert "Not found" == json_response(conn, 404) end end describe "/api/pleroma/admin/users/follow" do - test "allows to force-follow another user" do - admin = insert(:user, is_admin: true) + test "allows to force-follow another user", %{admin: admin, conn: conn} do user = insert(:user) follower = insert(:user) - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/follow", %{ "follower" => follower.nickname, @@ -328,15 +368,13 @@ test "allows to force-follow another user" do end describe "/api/pleroma/admin/users/unfollow" do - test "allows to force-unfollow another user" do - admin = insert(:user, is_admin: true) + test "allows to force-unfollow another user", %{admin: admin, conn: conn} do user = insert(:user) follower = insert(:user) User.follow(follower, user) - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/unfollow", %{ "follower" => follower.nickname, @@ -356,23 +394,20 @@ test "allows to force-unfollow another user" do end describe "PUT /api/pleroma/admin/users/tag" do - setup do - admin = insert(:user, is_admin: true) + setup %{conn: conn} do user1 = insert(:user, %{tags: ["x"]}) user2 = insert(:user, %{tags: ["y"]}) user3 = insert(:user, %{tags: ["unchanged"]}) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> put( - "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ - user2.nickname - }&tags[]=foo&tags[]=bar" + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=foo&tags[]=bar" ) - %{conn: conn, admin: admin, user1: user1, user2: user2, user3: user3} + %{conn: conn, user1: user1, user2: user2, user3: user3} end test "it appends specified tags to users with specified nicknames", %{ @@ -405,23 +440,20 @@ test "it does not modify tags of not specified users", %{conn: conn, user3: user end describe "DELETE /api/pleroma/admin/users/tag" do - setup do - admin = insert(:user, is_admin: true) + setup %{conn: conn} do user1 = insert(:user, %{tags: ["x"]}) user2 = insert(:user, %{tags: ["y", "z"]}) user3 = insert(:user, %{tags: ["unchanged"]}) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete( - "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ - user2.nickname - }&tags[]=x&tags[]=z" + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=x&tags[]=z" ) - %{conn: conn, admin: admin, user1: user1, user2: user2, user3: user3} + %{conn: conn, user1: user1, user2: user2, user3: user3} end test "it removes specified tags from users with specified nicknames", %{ @@ -454,12 +486,9 @@ test "it does not modify tags of not specified users", %{conn: conn, user3: user end describe "/api/pleroma/admin/users/:nickname/permission_group" do - test "GET is giving user_info" do - admin = insert(:user, is_admin: true) - + test "GET is giving user_info", %{admin: admin, conn: conn} do conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/") @@ -469,13 +498,11 @@ test "GET is giving user_info" do } end - test "/:right POST, can add to a permission group" do - admin = insert(:user, is_admin: true) + test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") @@ -489,22 +516,18 @@ test "/:right POST, can add to a permission group" do "@#{admin.nickname} made @#{user.nickname} admin" end - test "/:right POST, can add to a permission group (multiple)" do - admin = insert(:user, is_admin: true) + test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do user_one = insert(:user) user_two = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> post("/api/pleroma/admin/users/permission_group/admin", %{ nicknames: [user_one.nickname, user_two.nickname] }) - assert json_response(conn, 200) == %{ - "is_admin" => true - } + assert json_response(conn, 200) == %{"is_admin" => true} log_entry = Repo.one(ModerationLog) @@ -512,19 +535,15 @@ test "/:right POST, can add to a permission group (multiple)" do "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin" end - test "/:right DELETE, can remove from a permission group" do - admin = insert(:user, is_admin: true) + test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do user = insert(:user, is_admin: true) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") - assert json_response(conn, 200) == %{ - "is_admin" => false - } + assert json_response(conn, 200) == %{"is_admin" => false} log_entry = Repo.one(ModerationLog) @@ -532,22 +551,21 @@ test "/:right DELETE, can remove from a permission group" do "@#{admin.nickname} revoked admin role from @#{user.nickname}" end - test "/:right DELETE, can remove from a permission group (multiple)" do - admin = insert(:user, is_admin: true) + test "/:right DELETE, can remove from a permission group (multiple)", %{ + admin: admin, + conn: conn + } do user_one = insert(:user, is_admin: true) user_two = insert(:user, is_admin: true) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> delete("/api/pleroma/admin/users/permission_group/admin", %{ nicknames: [user_one.nickname, user_two.nickname] }) - assert json_response(conn, 200) == %{ - "is_admin" => false - } + assert json_response(conn, 200) == %{"is_admin" => false} log_entry = Repo.one(ModerationLog) @@ -559,10 +577,6 @@ test "/:right DELETE, can remove from a permission group (multiple)" do end describe "POST /api/pleroma/admin/email_invite, with valid config" do - setup do - [user: insert(:user, is_admin: true)] - end - clear_config([:instance, :registrations_open]) do Pleroma.Config.put([:instance, :registrations_open], false) end @@ -571,14 +585,13 @@ test "/:right DELETE, can remove from a permission group (multiple)" do Pleroma.Config.put([:instance, :invites_enabled], true) end - test "sends invitation and returns 204", %{conn: conn, user: user} do + test "sends invitation and returns 204", %{admin: admin, conn: conn} do recipient_email = "foo@bar.com" recipient_name = "J. D." conn = - conn - |> assign(:user, user) - |> post( + post( + conn, "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}" ) @@ -593,7 +606,7 @@ test "sends invitation and returns 204", %{conn: conn, user: user} do email = Pleroma.Emails.UserEmail.user_invitation_email( - user, + admin, token_record, recipient_email, recipient_name @@ -606,12 +619,14 @@ test "sends invitation and returns 204", %{conn: conn, user: user} do ) end - test "it returns 403 if requested by a non-admin", %{conn: conn} do + test "it returns 403 if requested by a non-admin" do non_admin_user = insert(:user) + token = insert(:oauth_token, user: non_admin_user) conn = - conn + build_conn() |> assign(:user, non_admin_user) + |> assign(:token, token) |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :forbidden) @@ -619,45 +634,33 @@ test "it returns 403 if requested by a non-admin", %{conn: conn} do end describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do - setup do - [user: insert(:user, is_admin: true)] - end - clear_config([:instance, :registrations_open]) clear_config([:instance, :invites_enabled]) - test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn, user: user} do + test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do Pleroma.Config.put([:instance, :registrations_open], false) Pleroma.Config.put([:instance, :invites_enabled], false) - conn = - conn - |> assign(:user, user) - |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") + conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :internal_server_error) end - test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: user} do + test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do Pleroma.Config.put([:instance, :registrations_open], true) Pleroma.Config.put([:instance, :invites_enabled], true) - conn = - conn - |> assign(:user, user) - |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") + conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") assert json_response(conn, :internal_server_error) end end - test "/api/pleroma/admin/users/:nickname/password_reset" do - admin = insert(:user, is_admin: true) + test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do user = insert(:user) conn = - build_conn() - |> assign(:user, admin) + conn |> put_req_header("accept", "application/json") |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset") @@ -667,16 +670,6 @@ test "/api/pleroma/admin/users/:nickname/password_reset" do end describe "GET /api/pleroma/admin/users" do - setup do - admin = insert(:user, is_admin: true) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn, admin: admin} - end - test "renders users array for the first page", %{conn: conn, admin: admin} do user = insert(:user, local: false, tags: ["foo", "bar"]) conn = get(conn, "/api/pleroma/admin/users?page=1") @@ -898,6 +891,7 @@ test "regular search with page size", %{conn: conn} do test "only local users" do admin = insert(:user, is_admin: true, nickname: "john") + token = insert(:oauth_admin_token, user: admin) user = insert(:user, nickname: "bob") insert(:user, nickname: "bobb", local: false) @@ -905,6 +899,7 @@ test "only local users" do conn = build_conn() |> assign(:user, admin) + |> assign(:token, token) |> get("/api/pleroma/admin/users?query=bo&filters=local") assert json_response(conn, 200) == %{ @@ -926,16 +921,13 @@ test "only local users" do } end - test "only local users with no query", %{admin: old_admin} do + test "only local users with no query", %{conn: conn, admin: old_admin} do admin = insert(:user, is_admin: true, nickname: "john") user = insert(:user, nickname: "bob") insert(:user, nickname: "bobb", local: false) - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/users?filters=local") + conn = get(conn, "/api/pleroma/admin/users?filters=local") users = [ @@ -1093,6 +1085,7 @@ test "load users with tags list", %{conn: conn} do test "it works with multiple filters" do admin = insert(:user, nickname: "john", is_admin: true) + token = insert(:oauth_admin_token, user: admin) user = insert(:user, nickname: "bob", local: false, deactivated: true) insert(:user, nickname: "ken", local: true, deactivated: true) @@ -1101,6 +1094,7 @@ test "it works with multiple filters" do conn = build_conn() |> assign(:user, admin) + |> assign(:token, token) |> get("/api/pleroma/admin/users?filters=deactivated,external") assert json_response(conn, 200) == %{ @@ -1122,13 +1116,10 @@ test "it works with multiple filters" do } end - test "it omits relay user", %{admin: admin} do + test "it omits relay user", %{admin: admin, conn: conn} do assert %User{} = Relay.get_actor() - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/users") + conn = get(conn, "/api/pleroma/admin/users") assert json_response(conn, 200) == %{ "count" => 1, @@ -1150,15 +1141,13 @@ test "it omits relay user", %{admin: admin} do end end - test "PATCH /api/pleroma/admin/users/activate" do - admin = insert(:user, is_admin: true) + test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do user_one = insert(:user, deactivated: true) user_two = insert(:user, deactivated: true) conn = - build_conn() - |> assign(:user, admin) - |> patch( + patch( + conn, "/api/pleroma/admin/users/activate", %{nicknames: [user_one.nickname, user_two.nickname]} ) @@ -1172,15 +1161,13 @@ test "PATCH /api/pleroma/admin/users/activate" do "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}" end - test "PATCH /api/pleroma/admin/users/deactivate" do - admin = insert(:user, is_admin: true) + test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do user_one = insert(:user, deactivated: false) user_two = insert(:user, deactivated: false) conn = - build_conn() - |> assign(:user, admin) - |> patch( + patch( + conn, "/api/pleroma/admin/users/deactivate", %{nicknames: [user_one.nickname, user_two.nickname]} ) @@ -1194,14 +1181,10 @@ test "PATCH /api/pleroma/admin/users/deactivate" do "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" end - test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do - admin = insert(:user, is_admin: true) + test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do user = insert(:user) - conn = - build_conn() - |> assign(:user, admin) - |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation") + conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation") assert json_response(conn, 200) == %{ @@ -1223,16 +1206,6 @@ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do end describe "POST /api/pleroma/admin/users/invite_token" do - setup do - admin = insert(:user, is_admin: true) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn} - end - test "without options", %{conn: conn} do conn = post(conn, "/api/pleroma/admin/users/invite_token") @@ -1287,16 +1260,6 @@ test "with max use and expires_at", %{conn: conn} do end describe "GET /api/pleroma/admin/users/invites" do - setup do - admin = insert(:user, is_admin: true) - - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn} - end - test "no invites", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/users/invites") @@ -1325,14 +1288,10 @@ test "with invite", %{conn: conn} do end describe "POST /api/pleroma/admin/users/revoke_invite" do - test "with token" do - admin = insert(:user, is_admin: true) + test "with token", %{conn: conn} do {:ok, invite} = UserInviteToken.create_invite() - conn = - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) + conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) assert json_response(conn, 200) == %{ "expires_at" => nil, @@ -1345,25 +1304,14 @@ test "with token" do } end - test "with invalid token" do - admin = insert(:user, is_admin: true) - - conn = - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) + test "with invalid token", %{conn: conn} do + conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) assert json_response(conn, :not_found) == "Not found" end end describe "GET /api/pleroma/admin/reports/:id" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin)} - end - test "returns report by its id", %{conn: conn} do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -1391,8 +1339,7 @@ test "returns 404 when report id is invalid", %{conn: conn} do end describe "PATCH /api/pleroma/admin/reports" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + setup do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -1411,9 +1358,7 @@ test "returns 404 when report id is invalid", %{conn: conn} do }) %{ - conn: assign(conn, :user, admin), id: report_id, - admin: admin, second_report_id: second_report_id } end @@ -1509,12 +1454,6 @@ test "updates state of multiple reports", %{ end describe "GET /api/pleroma/admin/reports" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin)} - end - test "returns empty response when no reports created", %{conn: conn} do response = conn @@ -1609,10 +1548,12 @@ test "returns reports with specified state", %{conn: conn} do test "returns 403 when requested by a non-admin" do user = insert(:user) + token = insert(:oauth_token, user: user) conn = build_conn() |> assign(:user, user) + |> assign(:token, token) |> get("/api/pleroma/admin/reports") assert json_response(conn, :forbidden) == @@ -1620,17 +1561,14 @@ test "returns 403 when requested by a non-admin" do end test "returns 403 when requested by anonymous" do - conn = - build_conn() - |> get("/api/pleroma/admin/reports") + conn = get(build_conn(), "/api/pleroma/admin/reports") assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."} end end describe "GET /api/pleroma/admin/grouped_reports" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + setup do [reporter, target_user] = insert_pair(:user) date1 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() @@ -1665,7 +1603,6 @@ test "returns 403 when requested by anonymous" do }) %{ - conn: assign(conn, :user, admin), first_status: Activity.get_by_ap_id_with_object(first_status.data["id"]), second_status: Activity.get_by_ap_id_with_object(second_status.data["id"]), third_status: Activity.get_by_ap_id_with_object(third_status.data["id"]), @@ -1833,11 +1770,10 @@ test "account not empty if status was deleted", %{ end describe "PUT /api/pleroma/admin/statuses/:id" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + setup do activity = insert(:note_activity) - %{conn: assign(conn, :user, admin), id: activity.id, admin: admin} + %{id: activity.id} end test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do @@ -1890,20 +1826,17 @@ test "change visibility flag", %{conn: conn, id: id, admin: admin} do end test "returns 400 when visibility is unknown", %{conn: conn, id: id} do - conn = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"}) + conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"}) assert json_response(conn, :bad_request) == "Unsupported visibility" end end describe "DELETE /api/pleroma/admin/statuses/:id" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + setup do activity = insert(:note_activity) - %{conn: assign(conn, :user, admin), id: activity.id, admin: admin} + %{id: activity.id} end test "deletes status", %{conn: conn, id: id, admin: admin} do @@ -1920,21 +1853,13 @@ test "deletes status", %{conn: conn, id: id, admin: admin} do end test "returns error when status is not exist", %{conn: conn} do - conn = - conn - |> delete("/api/pleroma/admin/statuses/test") + conn = delete(conn, "/api/pleroma/admin/statuses/test") assert json_response(conn, :bad_request) == "Could not delete" end end describe "GET /api/pleroma/admin/config" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin)} - end - test "without any settings in db", %{conn: conn} do conn = get(conn, "/api/pleroma/admin/config") @@ -1966,9 +1891,7 @@ test "with settings in db", %{conn: conn} do end describe "POST /api/pleroma/admin/config" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - + setup do temp_file = "config/test.exported_from_db.secret.exs" on_exit(fn -> @@ -1982,8 +1905,6 @@ test "with settings in db", %{conn: conn} do Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) :ok = File.rm(temp_file) end) - - %{conn: assign(conn, :user, admin)} end clear_config([:instance, :dynamic_configuration]) do @@ -2535,9 +2456,7 @@ test "delete part of settings by atom subkeys", %{conn: conn} do end describe "config mix tasks run" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - + setup do temp_file = "config/test.exported_from_db.secret.exs" Mix.shell(Mix.Shell.Quiet) @@ -2547,7 +2466,7 @@ test "delete part of settings by atom subkeys", %{conn: conn} do :ok = File.rm(temp_file) end) - %{conn: assign(conn, :user, admin), admin: admin} + :ok end clear_config([:instance, :dynamic_configuration]) do @@ -2558,25 +2477,21 @@ test "delete part of settings by atom subkeys", %{conn: conn} do Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: "…"}) end - test "transfer settings to DB and to file", %{conn: conn, admin: admin} do + test "transfer settings to DB and to file", %{conn: conn} do assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] - conn = get(conn, "/api/pleroma/admin/config/migrate_to_db") - assert json_response(conn, 200) == %{} + ret_conn = get(conn, "/api/pleroma/admin/config/migrate_to_db") + assert json_response(ret_conn, 200) == %{} assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0 - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/config/migrate_from_db") + ret_conn = get(conn, "/api/pleroma/admin/config/migrate_from_db") - assert json_response(conn, 200) == %{} + assert json_response(ret_conn, 200) == %{} assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == [] end end describe "GET /api/pleroma/admin/users/:nickname/statuses" do setup do - admin = insert(:user, is_admin: true) user = insert(:user) date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() @@ -2587,11 +2502,7 @@ test "transfer settings to DB and to file", %{conn: conn, admin: admin} do insert(:note_activity, user: user, published: date2) insert(:note_activity, user: user, published: date3) - conn = - build_conn() - |> assign(:user, admin) - - {:ok, conn: conn, user: user} + %{user: user} end test "renders user's statuses", %{conn: conn, user: user} do @@ -2632,11 +2543,10 @@ test "returns private statuses with godmode on", %{conn: conn, user: user} do end describe "GET /api/pleroma/admin/moderation_log" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + setup do moderator = insert(:user, is_moderator: true) - %{conn: assign(conn, :user, admin), admin: admin, moderator: moderator} + %{moderator: moderator} end test "returns the log", %{conn: conn, admin: admin} do @@ -2841,20 +2751,12 @@ test "returns log filtered by search", %{conn: conn, moderator: moderator} do end describe "PATCH /users/:nickname/force_password_reset" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) + test "sets password_reset_pending to true", %{conn: conn} do user = insert(:user) - - %{conn: assign(conn, :user, admin), admin: admin, user: user} - end - - test "sets password_reset_pending to true", %{admin: admin, user: user} do assert user.password_reset_pending == false conn = - build_conn() - |> assign(:user, admin) - |> patch("/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]}) + patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]}) assert json_response(conn, 204) == "" @@ -2865,17 +2767,9 @@ test "sets password_reset_pending to true", %{admin: admin, user: user} do end describe "relays" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin), admin: admin} - end - - test "POST /relay", %{admin: admin} do + test "POST /relay", %{conn: conn, admin: admin} do conn = - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/relay", %{ + post(conn, "/api/pleroma/admin/relay", %{ relay_url: "http://mastodon.example.org/users/admin" }) @@ -2887,7 +2781,7 @@ test "POST /relay", %{admin: admin} do "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" end - test "GET /relay", %{admin: admin} do + test "GET /relay", %{conn: conn} do relay_user = Pleroma.Web.ActivityPub.Relay.get_actor() ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"] @@ -2896,25 +2790,18 @@ test "GET /relay", %{admin: admin} do User.follow(relay_user, user) end) - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/relay") + conn = get(conn, "/api/pleroma/admin/relay") assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == [] end - test "DELETE /relay", %{admin: admin} do - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/relay", %{ + test "DELETE /relay", %{conn: conn, admin: admin} do + post(conn, "/api/pleroma/admin/relay", %{ relay_url: "http://mastodon.example.org/users/admin" }) conn = - build_conn() - |> assign(:user, admin) - |> delete("/api/pleroma/admin/relay", %{ + delete(conn, "/api/pleroma/admin/relay", %{ relay_url: "http://mastodon.example.org/users/admin" }) @@ -2931,63 +2818,48 @@ test "DELETE /relay", %{admin: admin} do end describe "instances" do - test "GET /instances/:instance/statuses" do - admin = insert(:user, is_admin: true) + test "GET /instances/:instance/statuses", %{conn: conn} do user = insert(:user, local: false, nickname: "archaeme@archae.me") user2 = insert(:user, local: false, nickname: "test@test.com") insert_pair(:note_activity, user: user) insert(:note_activity, user: user2) - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/instances/archae.me/statuses") + ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") - response = json_response(conn, 200) + response = json_response(ret_conn, 200) assert length(response) == 2 - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/instances/test.com/statuses") + ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses") - response = json_response(conn, 200) + response = json_response(ret_conn, 200) assert length(response) == 1 - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/instances/nonexistent.com/statuses") + ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses") - response = json_response(conn, 200) + response = json_response(ret_conn, 200) assert length(response) == 0 end end describe "PATCH /confirm_email" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin), admin: admin} - end - - test "it confirms emails of two users", %{admin: admin} do + test "it confirms emails of two users", %{conn: conn, admin: admin} do [first_user, second_user] = insert_pair(:user, confirmation_pending: true) assert first_user.confirmation_pending == true assert second_user.confirmation_pending == true - build_conn() - |> assign(:user, admin) - |> patch("/api/pleroma/admin/users/confirm_email", %{ - nicknames: [ - first_user.nickname, - second_user.nickname - ] - }) + ret_conn = + patch(conn, "/api/pleroma/admin/users/confirm_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + assert ret_conn.status == 200 assert first_user.confirmation_pending == true assert second_user.confirmation_pending == true @@ -3002,23 +2874,18 @@ test "it confirms emails of two users", %{admin: admin} do end describe "PATCH /resend_confirmation_email" do - setup %{conn: conn} do - admin = insert(:user, is_admin: true) - - %{conn: assign(conn, :user, admin), admin: admin} - end - - test "it resend emails for two users", %{admin: admin} do + test "it resend emails for two users", %{conn: conn, admin: admin} do [first_user, second_user] = insert_pair(:user, confirmation_pending: true) - build_conn() - |> assign(:user, admin) - |> patch("/api/pleroma/admin/users/resend_confirmation_email", %{ - nicknames: [ - first_user.nickname, - second_user.nickname - ] - }) + ret_conn = + patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{ + nicknames: [ + first_user.nickname, + second_user.nickname + ] + }) + + assert ret_conn.status == 200 log_entry = Repo.one(ModerationLog) @@ -3030,8 +2897,7 @@ test "it resend emails for two users", %{admin: admin} do end describe "POST /reports/:id/notes" do - setup do - admin = insert(:user, is_admin: true) + setup %{conn: conn, admin: admin} do [reporter, target_user] = insert_pair(:user) activity = insert(:note_activity, user: target_user) @@ -3042,22 +2908,17 @@ test "it resend emails for two users", %{admin: admin} do "status_ids" => [activity.id] }) - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ + post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ content: "this is disgusting!" }) - build_conn() - |> assign(:user, admin) - |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ + post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ content: "this is disgusting2!" }) %{ admin_id: admin.id, - report_id: report_id, - admin: admin + report_id: report_id } end @@ -3071,11 +2932,8 @@ test "it creates report note", %{admin_id: admin_id, report_id: report_id} do } = note end - test "it returns reports with notes", %{admin: admin} do - conn = - build_conn() - |> assign(:user, admin) - |> get("/api/pleroma/admin/reports") + test "it returns reports with notes", %{conn: conn, admin: admin} do + conn = get(conn, "/api/pleroma/admin/reports") response = json_response(conn, 200) notes = hd(response["reports"])["notes"] @@ -3087,14 +2945,12 @@ test "it returns reports with notes", %{admin: admin} do assert response["total"] == 1 end - test "it deletes the note", %{admin: admin, report_id: report_id} do + test "it deletes the note", %{conn: conn, report_id: report_id} do assert ReportNote |> Repo.all() |> length() == 2 [note, _] = Repo.all(ReportNote) - build_conn() - |> assign(:user, admin) - |> delete("/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") + delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") assert ReportNote |> Repo.all() |> length() == 1 end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 5fbe947ba..307221c5d 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -23,24 +23,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do clear_config([:instance, :allow_relay]) describe "posting statuses" do - setup do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - - [conn: conn] - end + setup do: oauth_access(["write:statuses"]) test "posting a status does not increment reblog_count when relaying", %{conn: conn} do Pleroma.Config.put([:instance, :federating], true) Pleroma.Config.get([:instance, :allow_relay], true) - user = insert(:user) response = conn - |> assign(:user, user) |> post("api/v1/statuses", %{ "content_type" => "text/plain", "source" => "Pleroma FE", @@ -54,7 +44,6 @@ test "posting a status does not increment reblog_count when relaying", %{conn: c response = conn - |> assign(:user, user) |> get("api/v1/statuses/#{response["id"]}", %{}) |> json_response(200) @@ -132,9 +121,7 @@ test "posting a status", %{conn: conn} do NaiveDateTime.to_iso8601(expiration.scheduled_at) end - test "posting an undefined status with an attachment", %{conn: conn} do - user = insert(:user) - + test "posting an undefined status with an attachment", %{user: user, conn: conn} do file = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), @@ -144,17 +131,14 @@ test "posting an undefined status with an attachment", %{conn: conn} do {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "media_ids" => [to_string(upload.id)] }) assert json_response(conn, 200) end - test "replying to a status", %{conn: conn} do - user = insert(:user) + test "replying to a status", %{user: user, conn: conn} do {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) conn = @@ -169,8 +153,10 @@ test "replying to a status", %{conn: conn} do assert Activity.get_in_reply_to_activity(activity).id == replied_to.id end - test "replying to a direct message with visibility other than direct", %{conn: conn} do - user = insert(:user) + 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 -> @@ -187,18 +173,14 @@ test "replying to a direct message with visibility other than direct", %{conn: c end test "posting a status with an invalid in_reply_to_id", %{conn: conn} do - conn = - conn - |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) + conn = post(conn, "/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) assert %{"content" => "xD", "id" => id} = json_response(conn, 200) assert Activity.get_by_id(id) end test "posting a sensitive status", %{conn: conn} do - conn = - conn - |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) + conn = post(conn, "/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) assert Activity.get_by_id(id) @@ -206,8 +188,7 @@ test "posting a sensitive status", %{conn: conn} do test "posting a fake status", %{conn: conn} do real_conn = - conn - |> post("/api/v1/statuses", %{ + post(conn, "/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" }) @@ -226,8 +207,7 @@ test "posting a fake status", %{conn: conn} do |> Kernel.put_in(["pleroma", "conversation_id"], nil) fake_conn = - conn - |> post("/api/v1/statuses", %{ + post(conn, "/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 @@ -254,8 +234,7 @@ test "posting a status with OGP link preview", %{conn: conn} do Config.put([:rich_media, :enabled], true) conn = - conn - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "https://example.com/ogp" }) @@ -267,9 +246,7 @@ test "posting a direct status", %{conn: conn} do user2 = insert(:user) content = "direct cofe @#{user2.nickname}" - conn = - conn - |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + conn = post(conn, "api/v1/statuses", %{"status" => content, "visibility" => "direct"}) assert %{"id" => id} = response = json_response(conn, 200) assert response["visibility"] == "direct" @@ -282,14 +259,13 @@ test "posting a direct status", %{conn: conn} do end describe "posting scheduled statuses" do + setup do: oauth_access(["write:statuses"]) + test "creates a scheduled activity", %{conn: conn} do - user = insert(:user) scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "scheduled", "scheduled_at" => scheduled_at }) @@ -299,8 +275,7 @@ test "creates a scheduled activity", %{conn: conn} do assert [] == Repo.all(Activity) end - test "creates a scheduled activity with a media attachment", %{conn: conn} do - user = insert(:user) + test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) file = %Plug.Upload{ @@ -312,9 +287,7 @@ test "creates a scheduled activity with a media attachment", %{conn: conn} do {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "media_ids" => [to_string(upload.id)], "status" => "scheduled", "scheduled_at" => scheduled_at @@ -326,15 +299,11 @@ test "creates a scheduled activity with a media attachment", %{conn: conn} do test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", %{conn: conn} do - user = insert(:user) - scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "not scheduled", "scheduled_at" => scheduled_at }) @@ -343,9 +312,7 @@ test "skips the scheduling and creates the activity if scheduled_at is earlier t assert [] == Repo.all(ScheduledActivity) end - test "returns error when daily user limit is exceeded", %{conn: conn} do - user = insert(:user) - + test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do today = NaiveDateTime.utc_now() |> NaiveDateTime.add(:timer.minutes(6), :millisecond) @@ -355,17 +322,12 @@ test "returns error when daily user limit is exceeded", %{conn: conn} do {:ok, _} = ScheduledActivity.create(user, attrs) {:ok, _} = ScheduledActivity.create(user, attrs) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) + conn = post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) end - test "returns error when total user limit is exceeded", %{conn: conn} do - user = insert(:user) - + test "returns error when total user limit is exceeded", %{user: user, conn: conn} do today = NaiveDateTime.utc_now() |> NaiveDateTime.add(:timer.minutes(6), :millisecond) @@ -382,23 +344,20 @@ test "returns error when total user limit is exceeded", %{conn: conn} do {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) + post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) assert %{"error" => "total limit exceeded"} == json_response(conn, 422) end end describe "posting polls" do + setup do: oauth_access(["write:statuses"]) + test "posting a poll", %{conn: conn} do - user = insert(:user) time = NaiveDateTime.utc_now() conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "Who is the #bestgrill?", "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} }) @@ -414,13 +373,10 @@ test "posting a poll", %{conn: conn} do end test "option limit is enforced", %{conn: conn} do - user = insert(:user) limit = Config.get([:instance, :poll_limits, :max_options]) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "desu~", "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} }) @@ -430,13 +386,10 @@ test "option limit is enforced", %{conn: conn} do end test "option character limit is enforced", %{conn: conn} do - user = insert(:user) limit = Config.get([:instance, :poll_limits, :max_option_chars]) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "...", "poll" => %{ "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], @@ -449,13 +402,10 @@ test "option character limit is enforced", %{conn: conn} do end test "minimal date limit is enforced", %{conn: conn} do - user = insert(:user) limit = Config.get([:instance, :poll_limits, :min_expiration]) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "imagine arbitrary limits", "poll" => %{ "options" => ["this post was made by pleroma gang"], @@ -468,13 +418,10 @@ test "minimal date limit is enforced", %{conn: conn} do end test "maximum date limit is enforced", %{conn: conn} do - user = insert(:user) limit = Config.get([:instance, :poll_limits, :max_expiration]) conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses", %{ + post(conn, "/api/v1/statuses", %{ "status" => "imagine arbitrary limits", "poll" => %{ "options" => ["this post was made by pleroma gang"], @@ -487,19 +434,18 @@ test "maximum date limit is enforced", %{conn: conn} do end end - test "get a status", %{conn: conn} do + test "get a status" do + %{conn: conn} = oauth_access(["read:statuses"]) activity = insert(:note_activity) - conn = - conn - |> get("/api/v1/statuses/#{activity.id}") + conn = get(conn, "/api/v1/statuses/#{activity.id}") assert %{"id" => id} = json_response(conn, 200) assert id == to_string(activity.id) end - test "get a direct status", %{conn: conn} do - user = insert(:user) + test "get a direct status" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) other_user = insert(:user) {:ok, activity} = @@ -516,7 +462,8 @@ test "get a direct status", %{conn: conn} do assert res["pleroma"]["direct_conversation_id"] == participation.id end - test "get statuses by IDs", %{conn: conn} do + test "get statuses by IDs" do + %{conn: conn} = oauth_access(["read:statuses"]) %{id: id1} = insert(:note_activity) %{id: id2} = insert(:note_activity) @@ -527,9 +474,9 @@ test "get statuses by IDs", %{conn: conn} do end describe "deleting a status" do - test "when you created it", %{conn: conn} do - activity = insert(:note_activity) - author = User.get_cached_by_ap_id(activity.data["actor"]) + test "when you created it" do + %{user: author, conn: conn} = oauth_access(["write:statuses"]) + activity = insert(:note_activity, user: author) conn = conn @@ -541,14 +488,11 @@ test "when you created it", %{conn: conn} do refute Activity.get_by_id(activity.id) end - test "when you didn't create it", %{conn: conn} do + test "when you didn't create it" do + %{conn: conn} = oauth_access(["write:statuses"]) activity = insert(:note_activity) - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/statuses/#{activity.id}") + conn = delete(conn, "/api/v1/statuses/#{activity.id}") assert %{"error" => _} = json_response(conn, 403) @@ -564,6 +508,7 @@ test "when you're an admin or moderator", %{conn: conn} do 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(res_conn, 200) @@ -571,6 +516,7 @@ test "when you're an admin or moderator", %{conn: conn} do 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(res_conn, 200) @@ -581,14 +527,12 @@ test "when you're an admin or moderator", %{conn: conn} do end describe "reblogging" do + setup do: oauth_access(["write:statuses"]) + test "reblogs and returns the reblogged status", %{conn: conn} do activity = insert(:note_activity) - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/reblog") + conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog") assert %{ "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, @@ -600,12 +544,8 @@ test "reblogs and returns the reblogged status", %{conn: conn} do test "reblogs privately and returns the reblogged status", %{conn: conn} do activity = insert(:note_activity) - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) + conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) assert %{ "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, @@ -616,7 +556,7 @@ test "reblogs privately and returns the reblogged status", %{conn: conn} do assert to_string(activity.id) == id end - test "reblogged status for another user", %{conn: conn} do + test "reblogged status for another user" do activity = insert(:note_activity) user1 = insert(:user) user2 = insert(:user) @@ -627,8 +567,9 @@ test "reblogged status for another user", %{conn: conn} do {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) conn_res = - conn + build_conn() |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) |> get("/api/v1/statuses/#{reblog_activity1.id}") assert %{ @@ -639,8 +580,9 @@ test "reblogged status for another user", %{conn: conn} do } = json_response(conn_res, 200) conn_res = - conn + build_conn() |> assign(:user, user2) + |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"])) |> get("/api/v1/statuses/#{reblog_activity1.id}") assert %{ @@ -654,28 +596,21 @@ test "reblogged status for another user", %{conn: conn} do end test "returns 400 error when activity is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/foo/reblog") + conn = post(conn, "/api/v1/statuses/foo/reblog") assert json_response(conn, 400) == %{"error" => "Could not repeat"} end end describe "unreblogging" do - test "unreblogs and returns the unreblogged status", %{conn: conn} do + setup do: oauth_access(["write:statuses"]) + + test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do activity = insert(:note_activity) - user = insert(:user) {:ok, _, _} = CommonAPI.repeat(activity.id, user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unreblog") + conn = post(conn, "/api/v1/statuses/#{activity.id}/unreblog") assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) @@ -683,26 +618,19 @@ test "unreblogs and returns the unreblogged status", %{conn: conn} do end test "returns 400 error when activity is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/foo/unreblog") + conn = post(conn, "/api/v1/statuses/foo/unreblog") assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} 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) - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/favourite") + conn = post(conn, "/api/v1/statuses/#{activity.id}/favourite") assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = json_response(conn, 200) @@ -711,28 +639,21 @@ test "favs a status and returns it", %{conn: conn} do end test "returns 400 error for a wrong id", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/favourite") + conn = post(conn, "/api/v1/statuses/1/favourite") assert json_response(conn, 400) == %{"error" => "Could not favorite"} end end describe "unfavoriting" do - test "unfavorites a status and returns it", %{conn: conn} do + setup do: oauth_access(["write:favourites"]) + + test "unfavorites a status and returns it", %{user: user, conn: conn} do activity = insert(:note_activity) - user = insert(:user) {:ok, _, _} = CommonAPI.favorite(activity.id, user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/unfavourite") + conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite") assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = json_response(conn, 200) @@ -741,23 +662,19 @@ test "unfavorites a status and returns it", %{conn: conn} do end test "returns 400 error for a wrong id", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/unfavourite") + conn = post(conn, "/api/v1/statuses/1/unfavourite") assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} end end describe "pinned statuses" do - setup do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + + setup %{user: user} do {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) - [user: user, activity: activity] + %{activity: activity} end clear_config([:instance, :max_pinned_statuses]) do @@ -769,13 +686,11 @@ test "pin status", %{conn: conn, user: user, activity: activity} do assert %{"id" => ^id_str, "pinned" => true} = conn - |> assign(:user, user) |> post("/api/v1/statuses/#{activity.id}/pin") |> json_response(200) assert [%{"id" => ^id_str, "pinned" => true}] = conn - |> assign(:user, user) |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") |> json_response(200) end @@ -783,19 +698,16 @@ test "pin status", %{conn: conn, user: user, activity: activity} do 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 - |> assign(:user, user) - |> post("/api/v1/statuses/#{dm.id}/pin") + conn = post(conn, "/api/v1/statuses/#{dm.id}/pin") assert json_response(conn, 400) == %{"error" => "Could not pin"} 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) - user = refresh_record(user) assert %{"id" => ^id_str, "pinned" => false} = conn @@ -805,16 +717,12 @@ test "unpin status", %{conn: conn, user: user, activity: activity} do assert [] = conn - |> assign(:user, user) |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") |> json_response(200) end - test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/1/unpin") + test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do + conn = post(conn, "/api/v1/statuses/1/unpin") assert json_response(conn, 400) == %{"error" => "Could not unpin"} end @@ -826,7 +734,6 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do assert %{"id" => ^id_str_one, "pinned" => true} = conn - |> assign(:user, user) |> post("/api/v1/statuses/#{id_str_one}/pin") |> json_response(200) @@ -844,8 +751,7 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do setup do Config.put([:rich_media, :enabled], true) - user = insert(:user) - %{user: user} + oauth_access(["read:statuses"]) end test "returns rich-media card", %{conn: conn, user: user} do @@ -887,7 +793,6 @@ test "returns rich-media card", %{conn: conn, user: user} do response_two = conn - |> assign(:user, user) |> get("/api/v1/statuses/#{activity.id}/card") |> json_response(200) @@ -925,72 +830,55 @@ test "replaces missing description with an empty string", %{conn: conn, user: us end test "bookmarks" do - user = insert(:user) - for_user = insert(:user) + %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) + author = insert(:user) {:ok, activity1} = - CommonAPI.post(user, %{ + CommonAPI.post(author, %{ "status" => "heweoo?" }) {:ok, activity2} = - CommonAPI.post(user, %{ + CommonAPI.post(author, %{ "status" => "heweoo!" }) - response1 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity1.id}/bookmark") + response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark") assert json_response(response1, 200)["bookmarked"] == true - response2 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity2.id}/bookmark") + response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark") assert json_response(response2, 200)["bookmarked"] == true - bookmarks = - build_conn() - |> assign(:user, for_user) - |> get("/api/v1/bookmarks") + bookmarks = get(conn, "/api/v1/bookmarks") assert [json_response(response2, 200), json_response(response1, 200)] == json_response(bookmarks, 200) - response1 = - build_conn() - |> assign(:user, for_user) - |> post("/api/v1/statuses/#{activity1.id}/unbookmark") + response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark") assert json_response(response1, 200)["bookmarked"] == false - bookmarks = - build_conn() - |> assign(:user, for_user) - |> get("/api/v1/bookmarks") + bookmarks = get(conn, "/api/v1/bookmarks") assert [json_response(response2, 200)] == json_response(bookmarks, 200) end describe "conversation muting" do + setup do: oauth_access(["write:mutes"]) + setup do post_user = insert(:user) - user = insert(:user) - {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) - - [user: user, activity: activity] + %{activity: activity} end - test "mute conversation", %{conn: conn, user: user, activity: activity} do + test "mute conversation", %{conn: conn, activity: activity} do id_str = to_string(activity.id) assert %{"id" => ^id_str, "muted" => true} = conn - |> assign(:user, user) |> post("/api/v1/statuses/#{activity.id}/mute") |> json_response(200) end @@ -998,10 +886,7 @@ test "mute conversation", %{conn: conn, user: user, activity: activity} do test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do {:ok, _} = CommonAPI.add_mute(user, activity) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/statuses/#{activity.id}/mute") + conn = post(conn, "/api/v1/statuses/#{activity.id}/mute") assert json_response(conn, 400) == %{"error" => "conversation is already muted"} end @@ -1010,11 +895,10 @@ test "unmute conversation", %{conn: conn, user: user, activity: activity} do {:ok, _} = CommonAPI.add_mute(user, activity) id_str = to_string(activity.id) - user = refresh_record(user) assert %{"id" => ^id_str, "muted" => false} = conn - |> assign(:user, user) + # |> assign(:user, user) |> post("/api/v1/statuses/#{activity.id}/unmute") |> json_response(200) end @@ -1031,6 +915,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c conn1 = conn |> assign(:user, user2) + |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"])) |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) @@ -1044,6 +929,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c conn2 = conn |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"])) |> post("/api/v1/statuses/#{activity.id}/reblog") assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = @@ -1055,6 +941,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c conn3 = conn |> assign(:user, user3) + |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) |> get("api/v1/timelines/home") [reblogged_activity] = json_response(conn3, 200) @@ -1066,15 +953,12 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c end describe "GET /api/v1/statuses/:id/favourited_by" do - setup do - user = insert(:user) + setup do: oauth_access(["read:accounts"]) + + setup %{user: user} do {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - conn = - build_conn() - |> assign(:user, user) - - [conn: conn, activity: activity, user: user] + %{activity: activity} end test "returns users who have favorited the status", %{conn: conn, activity: activity} do @@ -1114,20 +998,18 @@ test "does not return users who have favorited the status but are blocked", %{ response = conn - |> assign(:user, user) |> get("/api/v1/statuses/#{activity.id}/favourited_by") |> json_response(:ok) assert Enum.empty?(response) end - test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do + test "does not fail on an unauthenticated request", %{activity: activity} do other_user = insert(:user) {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) response = - conn - |> assign(:user, nil) + build_conn() |> get("/api/v1/statuses/#{activity.id}/favourited_by") |> json_response(:ok) @@ -1135,7 +1017,7 @@ test "does not fail on an unauthenticated request", %{conn: conn, activity: acti assert id == other_user.id end - test "requires authentification for private posts", %{conn: conn, user: user} do + test "requires authentication for private posts", %{user: user} do other_user = insert(:user) {:ok, activity} = @@ -1146,15 +1028,25 @@ test "requires authentification for private posts", %{conn: conn, user: user} do {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by" + + build_conn() + |> get(favourited_by_url) + |> json_response(404) + + conn = + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + conn - |> assign(:user, nil) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") + |> assign(:token, nil) + |> get(favourited_by_url) |> json_response(404) response = - build_conn() - |> assign(:user, other_user) - |> get("/api/v1/statuses/#{activity.id}/favourited_by") + conn + |> get(favourited_by_url) |> json_response(200) [%{"id" => id}] = response @@ -1163,15 +1055,12 @@ test "requires authentification for private posts", %{conn: conn, user: user} do end describe "GET /api/v1/statuses/:id/reblogged_by" do - setup do - user = insert(:user) + setup do: oauth_access(["read:accounts"]) + + setup %{user: user} do {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - conn = - build_conn() - |> assign(:user, user) - - [conn: conn, activity: activity, user: user] + %{activity: activity} end test "returns users who have reblogged the status", %{conn: conn, activity: activity} do @@ -1211,7 +1100,6 @@ test "does not return users who have reblogged the status but are blocked", %{ response = conn - |> assign(:user, user) |> get("/api/v1/statuses/#{activity.id}/reblogged_by") |> json_response(:ok) @@ -1219,7 +1107,7 @@ test "does not return users who have reblogged the status but are blocked", %{ end test "does not return users who have reblogged the status privately", %{ - conn: %{assigns: %{user: user}} = conn, + conn: conn, activity: activity } do other_user = insert(:user) @@ -1228,20 +1116,18 @@ test "does not return users who have reblogged the status privately", %{ response = conn - |> assign(:user, user) |> get("/api/v1/statuses/#{activity.id}/reblogged_by") |> json_response(:ok) assert Enum.empty?(response) end - test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do + test "does not fail on an unauthenticated request", %{activity: activity} do other_user = insert(:user) {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) response = - conn - |> assign(:user, nil) + build_conn() |> get("/api/v1/statuses/#{activity.id}/reblogged_by") |> json_response(:ok) @@ -1249,7 +1135,7 @@ test "does not fail on an unauthenticated request", %{conn: conn, activity: acti assert id == other_user.id end - test "requires authentification for private posts", %{conn: conn, user: user} do + test "requires authentication for private posts", %{user: user} do other_user = insert(:user) {:ok, activity} = @@ -1258,14 +1144,14 @@ test "requires authentification for private posts", %{conn: conn, user: user} do "visibility" => "direct" }) - conn - |> assign(:user, nil) + build_conn() |> get("/api/v1/statuses/#{activity.id}/reblogged_by") |> json_response(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(200) @@ -1284,7 +1170,6 @@ test "context" do response = build_conn() - |> assign(:user, nil) |> get("/api/v1/statuses/#{id3}/context") |> json_response(:ok) @@ -1294,8 +1179,8 @@ test "context" do } = response end - test "returns the favorites of a user", %{conn: conn} do - user = insert(:user) + 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"}) @@ -1303,10 +1188,7 @@ test "returns the favorites of a user", %{conn: conn} do {:ok, _, _} = CommonAPI.favorite(activity.id, user) - first_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites") + first_conn = get(conn, "/api/v1/favourites") assert [status] = json_response(first_conn, 200) assert status["id"] == to_string(activity.id) @@ -1325,18 +1207,12 @@ test "returns the favorites of a user", %{conn: conn} do last_like = status["id"] - second_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites?since_id=#{last_like}") + second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}") assert [second_status] = json_response(second_conn, 200) assert second_status["id"] == to_string(second_activity.id) - third_conn = - conn - |> assign(:user, user) - |> get("/api/v1/favourites?limit=0") + third_conn = get(conn, "/api/v1/favourites?limit=0") assert [] = json_response(third_conn, 200) end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index 901f2ae41..9cc534f57 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -450,7 +450,7 @@ test "properly handles internal calls with `authorization`-wrapped params", %{ test "renders authentication page if user is already authenticated but `force_login` is tru-ish", %{app: app, conn: conn} do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -474,7 +474,7 @@ test "renders authentication page if user is already authenticated but user requ app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -497,7 +497,7 @@ test "with existing authentication and non-OOB `redirect_uri`, redirects to app app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -523,7 +523,7 @@ test "with existing authentication and unlisted non-OOB `redirect_uri`, redirect conn: conn } do unlisted_redirect_uri = "http://cross-site-request.com" - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn @@ -547,7 +547,7 @@ test "with existing authentication and OOB `redirect_uri`, redirects to app with app: app, conn: conn } do - token = insert(:oauth_token, app_id: app.id) + token = insert(:oauth_token, app: app) conn = conn diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index b1b59beed..3f7ef13bc 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -23,6 +23,7 @@ test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do result = conn |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) |> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "☕"}) assert %{"id" => id} = json_response(result, 200) @@ -39,6 +40,7 @@ test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do result = conn |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) |> post("/api/v1/pleroma/statuses/#{activity.id}/unreact_with_emoji", %{"emoji" => "☕"}) assert %{"id" => id} = json_response(result, 200) @@ -55,6 +57,11 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + conn = + conn + |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:statuses"])) + result = conn |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") @@ -73,9 +80,9 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do assert represented_user["id"] == other_user.id end - test "/api/v1/pleroma/conversations/:id", %{conn: conn} do + test "/api/v1/pleroma/conversations/:id" do user = insert(:user) - other_user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) @@ -84,16 +91,15 @@ test "/api/v1/pleroma/conversations/:id", %{conn: conn} do result = conn - |> assign(:user, other_user) |> get("/api/v1/pleroma/conversations/#{participation.id}") |> json_response(200) assert result["id"] == participation.id |> to_string() end - test "/api/v1/pleroma/conversations/:id/statuses", %{conn: conn} do + test "/api/v1/pleroma/conversations/:id/statuses" do user = insert(:user) - other_user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) third_user = insert(:user) {:ok, _activity} = @@ -113,7 +119,6 @@ test "/api/v1/pleroma/conversations/:id/statuses", %{conn: conn} do result = conn - |> assign(:user, other_user) |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") |> json_response(200) @@ -124,8 +129,8 @@ test "/api/v1/pleroma/conversations/:id/statuses", %{conn: conn} do assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result end - test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do - user = insert(:user) + test "PATCH /api/v1/pleroma/conversations/:id" do + %{user: user, conn: conn} = oauth_access(["write:conversations"]) other_user = insert(:user) {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi", "visibility" => "direct"}) @@ -140,7 +145,6 @@ test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do result = conn - |> assign(:user, user) |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ "recipients" => [user.id, other_user.id] }) @@ -155,9 +159,9 @@ test "PATCH /api/v1/pleroma/conversations/:id", %{conn: conn} do assert other_user in participation.recipients end - test "POST /api/v1/pleroma/conversations/read", %{conn: conn} do + test "POST /api/v1/pleroma/conversations/read" do user = insert(:user) - other_user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["write:notifications"]) {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"}) @@ -172,7 +176,6 @@ test "POST /api/v1/pleroma/conversations/read", %{conn: conn} do [%{"unread" => false}, %{"unread" => false}] = conn - |> assign(:user, other_user) |> post("/api/v1/pleroma/conversations/read", %{}) |> json_response(200) @@ -183,8 +186,9 @@ test "POST /api/v1/pleroma/conversations/read", %{conn: conn} do end describe "POST /api/v1/pleroma/notifications/read" do - test "it marks a single notification as read", %{conn: conn} do - user1 = insert(:user) + setup do: oauth_access(["write:notifications"]) + + test "it marks a single notification as read", %{user: user1, conn: conn} do user2 = insert(:user) {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) @@ -193,7 +197,6 @@ test "it marks a single notification as read", %{conn: conn} do response = conn - |> assign(:user, user1) |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) |> json_response(:ok) @@ -202,8 +205,7 @@ test "it marks a single notification as read", %{conn: conn} do refute Repo.get(Notification, notification2.id).seen end - test "it marks multiple notifications as read", %{conn: conn} do - user1 = insert(:user) + test "it marks multiple notifications as read", %{user: user1, conn: conn} do user2 = insert(:user) {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) @@ -213,7 +215,6 @@ test "it marks multiple notifications as read", %{conn: conn} do [response1, response2] = conn - |> assign(:user, user1) |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) |> json_response(:ok) @@ -225,11 +226,8 @@ test "it marks multiple notifications as read", %{conn: conn} do end test "it returns error when notification not found", %{conn: conn} do - user1 = insert(:user) - response = conn - |> assign(:user, user1) |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) |> json_response(:bad_request) diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 734cd2211..9bfaba9d3 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -6,10 +6,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo - alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.Web.CommonAPI + import ExUnit.CaptureLog import Pleroma.Factory import Mock @@ -24,21 +24,20 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do clear_config([:user, :deny_follow_blocked]) describe "POST /api/pleroma/follow_import" do + setup do: oauth_access(["follow"]) + test "it returns HTTP 200", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) |> json_response(:ok) assert response == "job started" end - test "it imports follow lists from file", %{conn: conn} do - user1 = insert(:user) + test "it imports follow lists from file", %{user: user1, conn: conn} do user2 = insert(:user) with_mocks([ @@ -49,7 +48,6 @@ test "it imports follow lists from file", %{conn: conn} do ]) do response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) |> json_response(:ok) @@ -67,12 +65,10 @@ test "it imports follow lists from file", %{conn: conn} do end test "it imports new-style mastodon follow lists", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/follow_import", %{ "list" => "Account address,Show boosts\n#{user2.ap_id},true" }) @@ -81,7 +77,7 @@ test "it imports new-style mastodon follow lists", %{conn: conn} do assert response == "job started" end - test "requires 'follow' or 'write:follows' permissions", %{conn: conn} do + test "requires 'follow' or 'write:follows' permissions" do token1 = insert(:oauth_token, scopes: ["read", "write"]) token2 = insert(:oauth_token, scopes: ["follow"]) token3 = insert(:oauth_token, scopes: ["something"]) @@ -89,7 +85,7 @@ test "requires 'follow' or 'write:follows' permissions", %{conn: conn} do for token <- [token1, token2, token3] do conn = - conn + build_conn() |> put_req_header("authorization", "Bearer #{token.token}") |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) @@ -104,21 +100,21 @@ test "requires 'follow' or 'write:follows' permissions", %{conn: conn} do end describe "POST /api/pleroma/blocks_import" do + # Note: "follow" or "write:blocks" permission is required + setup do: oauth_access(["write:blocks"]) + test "it returns HTTP 200", %{conn: conn} do - user1 = insert(:user) user2 = insert(:user) response = conn - |> assign(:user, user1) |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) |> json_response(:ok) assert response == "job started" end - test "it imports blocks users from file", %{conn: conn} do - user1 = insert(:user) + test "it imports blocks users from file", %{user: user1, conn: conn} do user2 = insert(:user) user3 = insert(:user) @@ -127,7 +123,6 @@ test "it imports blocks users from file", %{conn: conn} do ]) do response = conn - |> assign(:user, user1) |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) |> json_response(:ok) @@ -146,18 +141,17 @@ test "it imports blocks users from file", %{conn: conn} do end describe "PUT /api/pleroma/notification_settings" do - test "it updates notification settings", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "it updates notification settings", %{user: user, conn: conn} do conn - |> assign(:user, user) |> put("/api/pleroma/notification_settings", %{ "followers" => false, "bar" => 1 }) |> json_response(:ok) - user = Repo.get(User, user.id) + user = refresh_record(user) assert %Pleroma.User.NotificationSetting{ followers: false, @@ -168,11 +162,8 @@ test "it updates notification settings", %{conn: conn} do } == user.notification_settings end - test "it update notificatin privacy option", %{conn: conn} do - user = insert(:user) - + test "it updates notification privacy option", %{user: user, conn: conn} do conn - |> assign(:user, user) |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"}) |> json_response(:ok) @@ -374,14 +365,14 @@ test "show follow page with error when user cannot fecth by `acct` link", %{conn end end - describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do - test "follows user", %{conn: conn} do - user = insert(:user) + describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user" do + setup do: oauth_access(["follow"]) + + test "follows user", %{user: user, conn: conn} do user2 = insert(:user) response = conn - |> assign(:user, user) |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) @@ -389,55 +380,63 @@ test "follows user", %{conn: conn} do assert user2.follower_address in User.following(user) end - test "returns error when user is deactivated", %{conn: conn} do + test "returns error when user is deactivated" do user = insert(:user, deactivated: true) user2 = insert(:user) response = - conn + build_conn() |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["follow"])) |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" end - test "returns error when user is blocked", %{conn: conn} do + test "returns error when user is blocked", %{user: user, conn: conn} do Pleroma.Config.put([:user, :deny_follow_blocked], true) - user = insert(:user) user2 = insert(:user) {:ok, _user_block} = Pleroma.User.block(user2, user) response = conn - |> assign(:user, user) |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" end - test "returns error when followee not found", %{conn: conn} do - user = insert(:user) + test "returns error on insufficient permissions", %{user: user, conn: conn} do + user2 = insert(:user) + for token <- [nil, insert(:oauth_token, user: user, scopes: ["read"])] do + response = + conn + |> assign(:token, token) + |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end + end + + test "returns error when followee not found", %{conn: conn} do response = conn - |> assign(:user, user) |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) |> response(200) assert response =~ "Error following account" end - test "returns success result when user already in followers", %{conn: conn} do - user = insert(:user) + test "returns success result when user already in followers", %{user: user, conn: conn} do user2 = insert(:user) {:ok, _, _, _} = CommonAPI.follow(user, user2) response = conn - |> assign(:user, refresh_record(user)) |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) @@ -445,7 +444,7 @@ test "returns success result when user already in followers", %{conn: conn} do end end - describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do + describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user" do test "follows", %{conn: conn} do user = insert(:user) user2 = insert(:user) @@ -552,7 +551,7 @@ test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do end end - test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do + test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do Pleroma.Config.put([:instance, :healthcheck], true) with_mock Pleroma.Healthcheck, @@ -574,12 +573,11 @@ test "returns 503 when healthcheck enabled and health is false", %{conn: conn} end describe "POST /api/pleroma/disable_account" do - test "it returns HTTP 200", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do response = conn - |> assign(:user, user) |> post("/api/pleroma/disable_account", %{"password" => "test"}) |> json_response(:ok) @@ -591,12 +589,11 @@ test "it returns HTTP 200", %{conn: conn} do assert user.deactivated == true end - test "it returns returns when password invalid", %{conn: conn} do + test "with valid permissions and invalid password, it returns an error", %{conn: conn} do user = insert(:user) response = conn - |> assign(:user, user) |> post("/api/pleroma/disable_account", %{"password" => "test1"}) |> json_response(:ok) @@ -666,7 +663,7 @@ test "it redirect to webfinger url", %{conn: conn} do "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" end - test "it renders form with error when use not found", %{conn: conn} do + test "it renders form with error when user not found", %{conn: conn} do user2 = insert(:user, ap_id: "shp@social.heldscal.la") response = @@ -691,29 +688,21 @@ test "it returns new captcha", %{conn: conn} do end end - defp with_credentials(conn, username, password) do - header_content = "Basic " <> Base.encode64("#{username}:#{password}") - put_req_header(conn, "authorization", header_content) - end - - defp valid_user(_context) do - user = insert(:user) - [user: user] - end - describe "POST /api/pleroma/change_email" do - setup [:valid_user] + setup do: oauth_access(["write:accounts"]) - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/change_email") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials and invalid password", %{conn: conn, user: current_user} do + test "without permissions", %{conn: conn} do conn = conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + |> assign(:token, nil) + |> post("/api/pleroma/change_email") + + assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} + end + + test "with proper permissions and invalid password", %{conn: conn} do + conn = + post(conn, "/api/pleroma/change_email", %{ "password" => "hi", "email" => "test@test.com" }) @@ -721,14 +710,11 @@ test "with credentials and invalid password", %{conn: conn, user: current_user} assert json_response(conn, 200) == %{"error" => "Invalid password."} end - test "with credentials, valid password and invalid email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and invalid email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "foobar" }) @@ -736,28 +722,22 @@ test "with credentials, valid password and invalid email", %{ assert json_response(conn, 200) == %{"error" => "Email has invalid format."} end - test "with credentials, valid password and no email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and no email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test" }) assert json_response(conn, 200) == %{"error" => "Email can't be blank."} end - test "with credentials, valid password and blank email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and blank email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "" }) @@ -765,16 +745,13 @@ test "with credentials, valid password and blank email", %{ assert json_response(conn, 200) == %{"error" => "Email can't be blank."} end - test "with credentials, valid password and non unique email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and non unique email", %{ + conn: conn } do user = insert(:user) conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => user.email }) @@ -782,14 +759,11 @@ test "with credentials, valid password and non unique email", %{ assert json_response(conn, 200) == %{"error" => "Email has already been taken."} end - test "with credentials, valid password and valid email", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and valid email", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_email", %{ + post(conn, "/api/pleroma/change_email", %{ "password" => "test", "email" => "cofe@foobar.com" }) @@ -799,18 +773,20 @@ test "with credentials, valid password and valid email", %{ end describe "POST /api/pleroma/change_password" do - setup [:valid_user] + setup do: oauth_access(["write:accounts"]) - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/change_password") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials and invalid password", %{conn: conn, user: current_user} do + test "without permissions", %{conn: conn} do conn = conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_password", %{ + |> assign(:token, nil) + |> post("/api/pleroma/change_password") + + assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} + end + + test "with proper permissions and invalid password", %{conn: conn} do + conn = + post(conn, "/api/pleroma/change_password", %{ "password" => "hi", "new_password" => "newpass", "new_password_confirmation" => "newpass" @@ -819,14 +795,12 @@ test "with credentials and invalid password", %{conn: conn, user: current_user} assert json_response(conn, 200) == %{"error" => "Invalid password."} end - test "with credentials, valid password and new password and confirmation not matching", %{ - conn: conn, - user: current_user - } do + test "with proper permissions, valid password and new password and confirmation not matching", + %{ + conn: conn + } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_password", %{ + post(conn, "/api/pleroma/change_password", %{ "password" => "test", "new_password" => "newpass", "new_password_confirmation" => "notnewpass" @@ -837,14 +811,11 @@ test "with credentials, valid password and new password and confirmation not mat } end - test "with credentials, valid password and invalid new password", %{ - conn: conn, - user: current_user + test "with proper permissions, valid password and invalid new password", %{ + conn: conn } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_password", %{ + post(conn, "/api/pleroma/change_password", %{ "password" => "test", "new_password" => "", "new_password_confirmation" => "" @@ -855,51 +826,48 @@ test "with credentials, valid password and invalid new password", %{ } end - test "with credentials, valid password and matching new password and confirmation", %{ + test "with proper permissions, valid password and matching new password and confirmation", %{ conn: conn, - user: current_user + user: user } do conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/change_password", %{ + post(conn, "/api/pleroma/change_password", %{ "password" => "test", "new_password" => "newpass", "new_password_confirmation" => "newpass" }) assert json_response(conn, 200) == %{"status" => "success"} - fetched_user = User.get_cached_by_id(current_user.id) + fetched_user = User.get_cached_by_id(user.id) assert Comeonin.Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true end end describe "POST /api/pleroma/delete_account" do - setup [:valid_user] + setup do: oauth_access(["write:accounts"]) - test "without credentials", %{conn: conn} do - conn = post(conn, "/api/pleroma/delete_account") - assert json_response(conn, 403) == %{"error" => "Invalid credentials."} - end - - test "with credentials and invalid password", %{conn: conn, user: current_user} do + test "without permissions", %{conn: conn} do conn = conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/delete_account", %{"password" => "hi"}) + |> assign(:token, nil) + |> post("/api/pleroma/delete_account") - assert json_response(conn, 200) == %{"error" => "Invalid password."} + assert json_response(conn, 403) == + %{"error" => "Insufficient permissions: write:accounts."} end - test "with credentials and valid password", %{conn: conn, user: current_user} do - conn = - conn - |> with_credentials(current_user.nickname, "test") - |> post("/api/pleroma/delete_account", %{"password" => "test"}) + test "with proper permissions and wrong or missing password", %{conn: conn} do + for params <- [%{"password" => "hi"}, %{}] do + ret_conn = post(conn, "/api/pleroma/delete_account", params) + + assert json_response(ret_conn, 200) == %{"error" => "Invalid password."} + end + end + + test "with proper permissions and valid password", %{conn: conn} do + conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"}) assert json_response(conn, 200) == %{"status" => "success"} - # Wait a second for the started task to end - :timer.sleep(1000) end end end From 6c39fa20b191f985a2be704089c20acbcfe0035a Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Tue, 17 Dec 2019 17:00:46 +0700 Subject: [PATCH 03/38] Add support for `account_id` param to filter notifications by the account --- CHANGELOG.md | 1 + lib/pleroma/web/mastodon_api/mastodon_api.ex | 11 ++++++++- .../notification_controller_test.exs | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c133cd9ec..f0274ca01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: `/api/v1/update_credentials` accepts `actor_type` field. - Captcha: Support native provider - Captcha: Enable by default +- Mastodon API: Add support for `account_id` param to filter notifications by the account ### Fixed diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index b1816370e..6c13d4df6 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -56,6 +56,7 @@ def get_notifications(user, params \\ %{}) do user |> Notification.for_user_query(options) |> restrict(:exclude_types, options) + |> restrict(:account_id, options) |> Pagination.fetch_paginated(params) end @@ -71,7 +72,8 @@ defp cast_params(params) do exclude_visibilities: {:array, :string}, reblogs: :boolean, with_muted: :boolean, - with_move: :boolean + with_move: :boolean, + account_id: :string } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) @@ -88,5 +90,12 @@ defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) end + defp restrict(query, :account_id, %{account_id: account_id}) do + case User.get_cached_by_id(account_id) do + %{ap_id: ap_id} -> where(query, [n, a], a.actor == ^ap_id) + _ -> where(query, [n, a], a.actor == "fake ap id") + end + end + defp restrict(query, _, _), do: query end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 6635ea7a2..3458776ab 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -463,6 +463,29 @@ test "see move notifications with `with_move` parameter", %{ assert length(json_response(conn, 200)) == 1 end + describe "from specified user" do + test "account_id", %{conn: conn} do + user = insert(:user) + %{id: account_id} = other_user1 = insert(:user) + other_user2 = insert(:user) + + {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"}) + {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"}) + + assert [%{"account" => %{"id" => ^account_id}}] = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: account_id}) + |> json_response(200) + + assert [] = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{account_id: "cofe"}) + |> json_response(200) + end + end + defp get_notification_id_by_activity(%{id: id}) do Notification |> Repo.get_by(activity_id: id) From 34d85f8a5473fe0f85e8a8e9e8f58e40b3964ba4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 19 Dec 2019 20:45:44 +0700 Subject: [PATCH 04/38] Return 404 if account to filter notifications from is not found --- .../controllers/notification_controller.ex | 17 +++++++++++++++++ lib/pleroma/web/mastodon_api/mastodon_api.ex | 11 ++++------- .../notification_controller_test.exs | 4 ++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 16759be6a..f2508aca4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -23,6 +23,23 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) # GET /api/v1/notifications + def index(conn, %{"account_id" => account_id} = params) do + case Pleroma.User.get_cached_by_id(account_id) do + %{ap_id: account_ap_id} -> + params = + params + |> Map.delete("account_id") + |> Map.put("account_ap_id", account_ap_id) + + index(conn, params) + + _ -> + conn + |> put_status(:not_found) + |> json(%{"error" => "Account is not found"}) + end + end + def index(%{assigns: %{user: user}} = conn, params) do notifications = MastodonAPI.get_notifications(user, params) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 6c13d4df6..390a2b190 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -56,7 +56,7 @@ def get_notifications(user, params \\ %{}) do user |> Notification.for_user_query(options) |> restrict(:exclude_types, options) - |> restrict(:account_id, options) + |> restrict(:account_ap_id, options) |> Pagination.fetch_paginated(params) end @@ -73,7 +73,7 @@ defp cast_params(params) do reblogs: :boolean, with_muted: :boolean, with_move: :boolean, - account_id: :string + account_ap_id: :string } changeset = cast({%{}, param_types}, params, Map.keys(param_types)) @@ -90,11 +90,8 @@ defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) end - defp restrict(query, :account_id, %{account_id: account_id}) do - case User.get_cached_by_id(account_id) do - %{ap_id: ap_id} -> where(query, [n, a], a.actor == ^ap_id) - _ -> where(query, [n, a], a.actor == "fake ap id") - end + defp restrict(query, :account_ap_id, %{account_ap_id: account_ap_id}) do + where(query, [n, a], a.actor == ^account_ap_id) end defp restrict(query, _, _), do: query diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 3458776ab..24d0d49ed 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -478,11 +478,11 @@ test "account_id", %{conn: conn} do |> get("/api/v1/notifications", %{account_id: account_id}) |> json_response(200) - assert [] = + assert %{"error" => "Account is not found"} = conn |> assign(:user, user) |> get("/api/v1/notifications", %{account_id: "cofe"}) - |> json_response(200) + |> json_response(404) end end From 455e072d27f28c39050b2dc24b346a8f2ef30f90 Mon Sep 17 00:00:00 2001 From: Ivan Tashkinov Date: Thu, 19 Dec 2019 17:23:27 +0300 Subject: [PATCH 05/38] [#2068] Introduced proper OAuth tokens usage to controller tests. --- lib/pleroma/web/masto_fe_controller.ex | 25 +- test/support/conn_case.ex | 6 +- test/web/masto_fe_controller_test.exs | 5 +- .../update_credentials_test.exs | 216 ++++------- .../controllers/account_controller_test.exs | 362 ++++++------------ .../conversation_controller_test.exs | 45 +-- .../domain_block_controller_test.exs | 22 +- .../controllers/filter_controller_test.exs | 42 +- .../follow_request_controller_test.exs | 28 +- .../controllers/list_controller_test.exs | 88 ++--- .../controllers/media_controller_test.exs | 22 +- .../notification_controller_test.exs | 156 ++++---- .../controllers/poll_controller_test.exs | 59 +-- .../controllers/report_controller_test.exs | 20 +- .../scheduled_activity_controller_test.exs | 52 +-- .../controllers/search_controller_test.exs | 7 +- .../suggestion_controller_test.exs | 16 +- .../controllers/timeline_controller_test.exs | 92 ++--- .../mastodon_api_controller_test.exs | 74 +--- .../controllers/account_controller_test.exs | 156 +++----- .../controllers/emoji_api_controller_test.exs | 42 +- .../controllers/mascot_controller_test.exs | 41 +- .../controllers/scrobble_controller_test.exs | 17 +- 23 files changed, 548 insertions(+), 1045 deletions(-) diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index ca261ad6e..9f7e4943c 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -20,18 +20,21 @@ defmodule Pleroma.Web.MastoFEController do plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index) @doc "GET /web/*path" - def index(%{assigns: %{user: user}} = conn, _params) do - token = get_session(conn, :oauth_token) + def index(%{assigns: %{user: user, token: token}} = conn, _params) + when not is_nil(user) and not is_nil(token) do + conn + |> put_layout(false) + |> render("index.html", + token: token.token, + user: user, + custom_emojis: Pleroma.Emoji.get_all() + ) + end - if user && token do - conn - |> put_layout(false) - |> render("index.html", token: token, user: user, custom_emojis: Pleroma.Emoji.get_all()) - else - conn - |> put_session(:return_to, conn.request_path) - |> redirect(to: "/web/login") - end + def index(conn, _params) do + conn + |> put_session(:return_to, conn.request_path) + |> redirect(to: "/web/login") end @doc "GET /web/manifest.json" diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 95bc2492a..22e72fc09 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -30,14 +30,14 @@ defmodule Pleroma.Web.ConnCase do @endpoint Pleroma.Web.Endpoint # Sets up OAuth access with specified scopes - defp oauth_access(scopes, opts \\ %{}) do + defp oauth_access(scopes, opts \\ []) do user = - Map.get_lazy(opts, :user, fn -> + Keyword.get_lazy(opts, :user, fn -> Pleroma.Factory.insert(:user) end) token = - Map.get_lazy(opts, :oauth_token, fn -> + Keyword.get_lazy(opts, :oauth_token, fn -> Pleroma.Factory.insert(:oauth_token, user: user, scopes: scopes) end) diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs index b5dbd4a25..f9870a852 100644 --- a/test/web/masto_fe_controller_test.exs +++ b/test/web/masto_fe_controller_test.exs @@ -18,6 +18,7 @@ test "put settings", %{conn: conn} do conn = conn |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"])) |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) assert _result = json_response(conn, 200) @@ -63,12 +64,12 @@ test "redirects not logged-in users to the login page on private instances", %{ end test "does not redirect logged in users to the login page", %{conn: conn, path: path} do - token = insert(:oauth_token) + token = insert(:oauth_token, scopes: ["read"]) conn = conn |> assign(:user, token.user) - |> put_session(:oauth_token, token.token) + |> assign(:token, token) |> get(path) assert conn.status == 200 diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index 77cfce4fa..09bdc46e0 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -12,13 +12,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do clear_config([:instance, :max_account_fields]) describe "updating credentials" do - test "sets user settings in a generic way", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + test "sets user settings in a generic way", %{conn: conn} do res_conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "pleroma_settings_store" => %{ pleroma_fe: %{ theme: "bla" @@ -26,10 +24,10 @@ test "sets user settings in a generic way", %{conn: conn} do } }) - assert user = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} + assert user_data = json_response(res_conn, 200) + assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} - user = Repo.get(User, user["id"]) + user = Repo.get(User, user_data["id"]) res_conn = conn @@ -42,15 +40,15 @@ test "sets user settings in a generic way", %{conn: conn} do } }) - assert user = json_response(res_conn, 200) + assert user_data = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == + assert user_data["pleroma"]["settings_store"] == %{ "pleroma_fe" => %{"theme" => "bla"}, "masto_fe" => %{"theme" => "bla"} } - user = Repo.get(User, user["id"]) + user = Repo.get(User, user_data["id"]) res_conn = conn @@ -63,9 +61,9 @@ test "sets user settings in a generic way", %{conn: conn} do } }) - assert user = json_response(res_conn, 200) + assert user_data = json_response(res_conn, 200) - assert user["pleroma"]["settings_store"] == + assert user_data["pleroma"]["settings_store"] == %{ "pleroma_fe" => %{"theme" => "bla"}, "masto_fe" => %{"theme" => "blub"} @@ -73,97 +71,67 @@ test "sets user settings in a generic way", %{conn: conn} do end test "updates the user's bio", %{conn: conn} do - user = insert(:user) user2 = insert(:user) conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "note" => "I drink #cofe with @#{user2.nickname}" }) - assert user = json_response(conn, 200) + assert user_data = json_response(conn, 200) - assert user["note"] == + assert user_data["note"] == ~s(I drink #cofe with @#{user2.nickname}) end test "updates the user's locking status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{locked: "true"}) - - assert user = json_response(conn, 200) - assert user["locked"] == true + assert user_data = json_response(conn, 200) + assert user_data["locked"] == true end - test "updates the user's allow_following_move", %{conn: conn} do - user = insert(:user) - + test "updates the user's allow_following_move", %{user: user, conn: conn} do assert user.allow_following_move == true - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{allow_following_move: "false"}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"}) assert refresh_record(user).allow_following_move == false - assert user = json_response(conn, 200) - assert user["pleroma"]["allow_following_move"] == false + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["allow_following_move"] == false end test "updates the user's default scope", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) - - assert user = json_response(conn, 200) - assert user["source"]["privacy"] == "cofe" + assert user_data = json_response(conn, 200) + assert user_data["source"]["privacy"] == "cofe" end test "updates the user's hide_followers status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"}) - - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_followers"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_followers"] == true end test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ hide_followers_count: "true", hide_follows_count: "true" }) - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_followers_count"] == true - assert user["pleroma"]["hide_follows_count"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_followers_count"] == true + assert user_data["pleroma"]["hide_follows_count"] == true end - test "updates the user's skip_thread_containment option", %{conn: conn} do - user = insert(:user) - + test "updates the user's skip_thread_containment option", %{user: user, conn: conn} do response = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) |> json_response(200) @@ -172,104 +140,68 @@ test "updates the user's skip_thread_containment option", %{conn: conn} do end test "updates the user's hide_follows status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"}) - - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_follows"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_follows"] == true end test "updates the user's hide_favorites status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) - - assert user = json_response(conn, 200) - assert user["pleroma"]["hide_favorites"] == true + assert user_data = json_response(conn, 200) + assert user_data["pleroma"]["hide_favorites"] == true end test "updates the user's show_role status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"}) - - assert user = json_response(conn, 200) - assert user["source"]["pleroma"]["show_role"] == false + assert user_data = json_response(conn, 200) + assert user_data["source"]["pleroma"]["show_role"] == false end test "updates the user's no_rich_text status", %{conn: conn} do - user = insert(:user) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) - - assert user = json_response(conn, 200) - assert user["source"]["pleroma"]["no_rich_text"] == true + assert user_data = json_response(conn, 200) + assert user_data["source"]["pleroma"]["no_rich_text"] == true end test "updates the user's name", %{conn: conn} do - user = insert(:user) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) + patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) - assert user = json_response(conn, 200) - assert user["display_name"] == "markorepairs" + assert user_data = json_response(conn, 200) + assert user_data["display_name"] == "markorepairs" end - test "updates the user's avatar", %{conn: conn} do - user = insert(:user) - + test "updates the user's avatar", %{user: user, conn: conn} do new_avatar = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) assert user_response = json_response(conn, 200) assert user_response["avatar"] != User.avatar_url(user) end - test "updates the user's banner", %{conn: conn} do - user = insert(:user) - + test "updates the user's banner", %{user: user, conn: conn} do new_header = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header}) + conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header}) assert user_response = json_response(conn, 200) assert user_response["header"] != User.banner_url(user) end test "updates the user's background", %{conn: conn} do - user = insert(:user) - new_header = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), @@ -277,9 +209,7 @@ test "updates the user's background", %{conn: conn} do } conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + patch(conn, "/api/v1/accounts/update_credentials", %{ "pleroma_background_image" => new_header }) @@ -287,13 +217,13 @@ test "updates the user's background", %{conn: conn} do assert user_response["pleroma"]["background_image"] end - test "requires 'write:accounts' permission", %{conn: conn} do + test "requires 'write:accounts' permission" do token1 = insert(:oauth_token, scopes: ["read"]) token2 = insert(:oauth_token, scopes: ["write", "follow"]) for token <- [token1, token2] do conn = - conn + build_conn() |> put_req_header("authorization", "Bearer #{token.token}") |> patch("/api/v1/accounts/update_credentials", %{}) @@ -306,53 +236,44 @@ test "requires 'write:accounts' permission", %{conn: conn} do end end - test "updates profile emojos", %{conn: conn} do - user = insert(:user) - + test "updates profile emojos", %{user: user, conn: conn} do note = "*sips :blank:*" name = "I am :firefox:" - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/accounts/update_credentials", %{ + ret_conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ "note" => note, "display_name" => name }) - assert json_response(conn, 200) + assert json_response(ret_conn, 200) - conn = - conn - |> get("/api/v1/accounts/#{user.id}") + conn = get(conn, "/api/v1/accounts/#{user.id}") - assert user = json_response(conn, 200) + assert user_data = json_response(conn, 200) - assert user["note"] == note - assert user["display_name"] == name - assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"] + assert user_data["note"] == note + assert user_data["display_name"] == name + assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user_data["emojis"] end test "update fields", %{conn: conn} do - user = insert(:user) - fields = [ %{"name" => "foo", "value" => ""}, %{"name" => "link", "value" => "cofe.io"} ] - account = + account_data = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(200) - assert account["fields"] == [ + assert account_data["fields"] == [ %{"name" => "foo", "value" => "bar"}, %{"name" => "link", "value" => ~S(cofe.io)} ] - assert account["source"]["fields"] == [ + assert account_data["source"]["fields"] == [ %{ "name" => "foo", "value" => "" @@ -372,7 +293,6 @@ test "update fields", %{conn: conn} do account = conn |> put_req_header("content-type", "application/x-www-form-urlencoded") - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", fields) |> json_response(200) @@ -398,7 +318,6 @@ test "update fields", %{conn: conn} do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -408,7 +327,6 @@ test "update fields", %{conn: conn} do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -421,7 +339,6 @@ test "update fields", %{conn: conn} do assert %{"error" => "Invalid request"} == conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(403) @@ -432,7 +349,6 @@ test "update fields", %{conn: conn} do account = conn - |> assign(:user, user) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> json_response(200) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index fa08ae4df..0d4860a42 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -87,6 +87,7 @@ test "respects limit_to_local_content == :unauthenticated for remote user nickna conn = build_conn() |> assign(:user, reading_user) + |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.nickname}") Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) @@ -144,8 +145,9 @@ test "returns 404 for internal.fetch actor", %{conn: conn} do end describe "user timelines" do - test "respects blocks", %{conn: conn} do - user_one = insert(:user) + setup do: oauth_access(["read:statuses"]) + + test "respects blocks", %{user: user_one, conn: conn} do user_two = insert(:user) user_three = insert(:user) @@ -154,46 +156,35 @@ test "respects blocks", %{conn: conn} do {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"}) {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three) - resp = - conn - |> get("/api/v1/accounts/#{user_two.id}/statuses") + resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") assert [%{"id" => id}] = json_response(resp, 200) assert id == activity.id # Even a blocked user will deliver the full user timeline, there would be - # no point in looking at a blocked users timeline otherwise - resp = - conn - |> assign(:user, user_one) - |> get("/api/v1/accounts/#{user_two.id}/statuses") + # no point in looking at a blocked users timeline otherwise + resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") assert [%{"id" => id}] = json_response(resp, 200) assert id == activity.id - resp = - conn - |> get("/api/v1/accounts/#{user_three.id}/statuses") - + # Third user's timeline includes the repeat when viewed by unauthenticated user + resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") assert [%{"id" => id}] = json_response(resp, 200) assert id == repeat.id - # When viewing a third user's timeline, the blocked users will NOT be - # shown. - resp = - conn - |> assign(:user, user_one) - |> get("/api/v1/accounts/#{user_three.id}/statuses") + # When viewing a third user's timeline, the blocked users' statuses will NOT be shown + resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses") assert [] = json_response(resp, 200) end - test "gets a users statuses", %{conn: conn} do + test "gets users statuses", %{conn: conn} do user_one = insert(:user) user_two = insert(:user) user_three = insert(:user) - {:ok, user_three} = User.follow(user_three, user_one) + {:ok, _user_three} = User.follow(user_three, user_one) {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) @@ -206,9 +197,7 @@ test "gets a users statuses", %{conn: conn} do {:ok, private_activity} = CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) - resp = - conn - |> get("/api/v1/accounts/#{user_one.id}/statuses") + resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") assert [%{"id" => id}] = json_response(resp, 200) assert id == to_string(activity.id) @@ -216,6 +205,7 @@ test "gets a users statuses", %{conn: conn} do resp = conn |> assign(:user, user_two) + |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) |> get("/api/v1/accounts/#{user_one.id}/statuses") assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) @@ -225,6 +215,7 @@ test "gets a users statuses", %{conn: conn} do resp = conn |> assign(:user, user_three) + |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"])) |> get("/api/v1/accounts/#{user_one.id}/statuses") assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) @@ -236,9 +227,7 @@ test "unimplemented pinned statuses feature", %{conn: conn} do note = insert(:note_activity) user = User.get_cached_by_ap_id(note.data["actor"]) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true") assert json_response(conn, 200) == [] end @@ -257,63 +246,51 @@ test "gets an users media", %{conn: conn} do {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(image_post.id) - conn = - build_conn() - |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) + conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(image_post.id) end - test "gets a user's statuses without reblogs", %{conn: conn} do - user = insert(:user) + test "gets a user's statuses without reblogs", %{user: user, conn: conn} do {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, _, _} = CommonAPI.repeat(post.id, user) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) end - test "filters user's statuses by a hashtag", %{conn: conn} do - user = insert(:user) + test "filters user's statuses by a hashtag", %{user: user, conn: conn} do {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) + conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(post.id) end - test "the user views their own timelines and excludes direct messages", %{conn: conn} do - user = insert(:user) + test "the user views their own timelines and excludes direct messages", %{ + user: user, + conn: conn + } do {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]}) + get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]}) assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(public_activity.id) @@ -321,46 +298,42 @@ test "the user views their own timelines and excludes direct messages", %{conn: end describe "followers" do - test "getting followers", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["read:accounts"]) + + test "getting followers", %{user: user, conn: conn} do other_user = insert(:user) {:ok, user} = User.follow(user, other_user) - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") + conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(user.id) end - test "getting followers, hide_followers", %{conn: conn} do - user = insert(:user) + test "getting followers, hide_followers", %{user: user, conn: conn} do other_user = insert(:user, hide_followers: true) {:ok, _user} = User.follow(user, other_user) - conn = - conn - |> get("/api/v1/accounts/#{other_user.id}/followers") + conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") assert [] == json_response(conn, 200) end - test "getting followers, hide_followers, same user requesting", %{conn: conn} do + test "getting followers, hide_followers, same user requesting" do user = insert(:user) other_user = insert(:user, hide_followers: true) {:ok, _user} = User.follow(user, other_user) conn = - conn + build_conn() |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{other_user.id}/followers") refute [] == json_response(conn, 200) end - test "getting followers, pagination", %{conn: conn} do - user = insert(:user) + test "getting followers, pagination", %{user: user, conn: conn} do follower1 = insert(:user) follower2 = insert(:user) follower3 = insert(:user) @@ -368,29 +341,19 @@ test "getting followers, pagination", %{conn: conn} do {:ok, _} = User.follow(follower2, user) {:ok, _} = User.follow(follower3, user) - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) assert id3 == follower3.id assert id2 == follower2.id - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) assert id2 == follower2.id assert id1 == follower1.id - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") + res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") assert [%{"id" => id2}] = json_response(res_conn, 200) assert id2 == follower2.id @@ -402,46 +365,47 @@ test "getting followers, pagination", %{conn: conn} do end describe "following" do - test "getting following", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["read:accounts"]) + + test "getting following", %{user: user, conn: conn} do other_user = insert(:user) {:ok, user} = User.follow(user, other_user) - conn = - conn - |> get("/api/v1/accounts/#{user.id}/following") + conn = get(conn, "/api/v1/accounts/#{user.id}/following") assert [%{"id" => id}] = json_response(conn, 200) assert id == to_string(other_user.id) end - test "getting following, hide_follows", %{conn: conn} do + test "getting following, hide_follows, other user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = - conn + build_conn() + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.id}/following") assert [] == json_response(conn, 200) end - test "getting following, hide_follows, same user requesting", %{conn: conn} do + test "getting following, hide_follows, same user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) {:ok, user} = User.follow(user, other_user) conn = - conn + build_conn() |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"])) |> get("/api/v1/accounts/#{user.id}/following") refute [] == json_response(conn, 200) end - test "getting following, pagination", %{conn: conn} do - user = insert(:user) + test "getting following, pagination", %{user: user, conn: conn} do following1 = insert(:user) following2 = insert(:user) following3 = insert(:user) @@ -449,29 +413,20 @@ test "getting following, pagination", %{conn: conn} do {:ok, _} = User.follow(user, following2) {:ok, _} = User.follow(user, following3) - conn = - conn - |> assign(:user, user) - - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") + res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) assert id3 == following3.id assert id2 == following2.id - res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") + res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) assert id2 == following2.id assert id1 == following1.id res_conn = - conn - |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") + get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") assert [%{"id" => id2}] = json_response(res_conn, 200) assert id2 == following2.id @@ -483,82 +438,52 @@ test "getting following, pagination", %{conn: conn} do end describe "follow/unfollow" do + setup do: oauth_access(["follow"]) + test "following / unfollowing a user", %{conn: conn} do - user = insert(:user) other_user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/follow") + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow") - assert %{"id" => _id, "following" => true} = json_response(conn, 200) + assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200) - user = User.get_cached_by_id(user.id) + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow") - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unfollow") + assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200) - assert %{"id" => _id, "following" => false} = json_response(conn, 200) - - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follows", %{"uri" => other_user.nickname}) + conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname}) assert %{"id" => id} = json_response(conn, 200) assert id == to_string(other_user.id) end test "following without reblogs" do - follower = insert(:user) + %{conn: conn} = oauth_access(["follow", "read:statuses"]) followed = insert(:user) other_user = insert(:user) - conn = - build_conn() - |> assign(:user, follower) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") + ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false") - assert %{"showing_reblogs" => false} = json_response(conn, 200) + assert %{"showing_reblogs" => false} = json_response(ret_conn, 200) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") + ret_conn = get(conn, "/api/v1/timelines/home") - assert [] == json_response(conn, 200) + assert [] == json_response(ret_conn, 200) - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") + ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true") - assert %{"showing_reblogs" => true} = json_response(conn, 200) + assert %{"showing_reblogs" => true} = json_response(ret_conn, 200) - conn = - build_conn() - |> assign(:user, User.get_cached_by_id(follower.id)) - |> get("/api/v1/timelines/home") + conn = get(conn, "/api/v1/timelines/home") expected_activity_id = reblog.id assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) end - test "following / unfollowing errors" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - + test "following / unfollowing errors", %{user: user, conn: conn} do # self follow conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") assert %{"error" => "Record not found"} = json_response(conn_res, 404) @@ -588,47 +513,34 @@ test "following / unfollowing errors" do end describe "mute/unmute" do + setup do: oauth_access(["write:mutes"]) + test "with notifications", %{conn: conn} do - user = insert(:user) other_user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute") + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute") - response = json_response(conn, 200) + response = json_response(ret_conn, 200) assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response - user = User.get_cached_by_id(user.id) - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") response = json_response(conn, 200) assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response end test "without notifications", %{conn: conn} do - user = insert(:user) other_user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) + ret_conn = + post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) - response = json_response(conn, 200) + response = json_response(ret_conn, 200) assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response - user = User.get_cached_by_id(user.id) - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unmute") + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") response = json_response(conn, 200) assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response @@ -639,8 +551,9 @@ test "without notifications", %{conn: conn} do setup do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + %{conn: conn} = oauth_access(["read:statuses"], user: user) - [user: user, activity: activity] + [conn: conn, user: user, activity: activity] end test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do @@ -648,7 +561,6 @@ test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do result = conn - |> assign(:user, user) |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") |> json_response(200) @@ -658,23 +570,15 @@ test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do end end - test "blocking / unblocking a user", %{conn: conn} do - user = insert(:user) + test "blocking / unblocking a user" do + %{conn: conn} = oauth_access(["follow"]) other_user = insert(:user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/block") + ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block") - assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) + assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200) - user = User.get_cached_by_id(user.id) - - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/accounts/#{other_user.id}/unblock") + conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock") assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) end @@ -693,8 +597,7 @@ test "blocking / unblocking a user", %{conn: conn} do test "Account registration via Application", %{conn: conn} do conn = - conn - |> post("/api/v1/apps", %{ + post(conn, "/api/v1/apps", %{ client_name: "client_name", redirect_uris: "urn:ietf:wg:oauth:2.0:oob", scopes: "read, write, follow" @@ -711,8 +614,7 @@ test "Account registration via Application", %{conn: conn} do } = json_response(conn, 200) conn = - conn - |> post("/oauth/token", %{ + post(conn, "/oauth/token", %{ grant_type: "client_credentials", client_id: client_id, client_secret: client_secret @@ -769,13 +671,13 @@ test "rate limit", %{conn: conn} do app_token = insert(:oauth_token, user: nil) conn = - put_req_header(conn, "authorization", "Bearer " <> app_token.token) + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) |> Map.put(:remote_ip, {15, 15, 15, 15}) for i <- 1..5 do conn = - conn - |> post("/api/v1/accounts", %{ + post(conn, "/api/v1/accounts", %{ username: "#{i}lain", email: "#{i}lain@example.org", password: "PlzDontHackLain", @@ -798,8 +700,7 @@ test "rate limit", %{conn: conn} do end conn = - conn - |> post("/api/v1/accounts", %{ + post(conn, "/api/v1/accounts", %{ username: "6lain", email: "6lain@example.org", password: "PlzDontHackLain", @@ -815,9 +716,7 @@ test "returns bad_request if missing required params", %{ } do app_token = insert(:oauth_token, user: nil) - conn = - conn - |> put_req_header("authorization", "Bearer " <> app_token.token) + conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token) res = post(conn, "/api/v1/accounts", valid_params) assert json_response(res, 200) @@ -836,9 +735,7 @@ test "returns bad_request if missing required params", %{ end test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do - conn = - conn - |> put_req_header("authorization", "Bearer " <> "invalid-token") + conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token") res = post(conn, "/api/v1/accounts", valid_params) assert json_response(res, 403) == %{"error" => "Invalid credentials"} @@ -846,15 +743,14 @@ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_ end describe "GET /api/v1/accounts/:id/lists - account_lists" do - test "returns lists to which the account belongs", %{conn: conn} do - user = insert(:user) + test "returns lists to which the account belongs" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) res = conn - |> assign(:user, user) |> get("/api/v1/accounts/#{other_user.id}/lists") |> json_response(200) @@ -863,13 +759,9 @@ test "returns lists to which the account belongs", %{conn: conn} do end describe "verify_credentials" do - test "verify_credentials", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") + test "verify_credentials" do + %{user: user, conn: conn} = oauth_access(["read:accounts"]) + conn = get(conn, "/api/v1/accounts/verify_credentials") response = json_response(conn, 200) @@ -878,25 +770,21 @@ test "verify_credentials", %{conn: conn} do assert id == to_string(user.id) end - test "verify_credentials default scope unlisted", %{conn: conn} do + test "verify_credentials default scope unlisted" do user = insert(:user, default_scope: "unlisted") + %{conn: conn} = oauth_access(["read:accounts"], user: user) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") + conn = get(conn, "/api/v1/accounts/verify_credentials") assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) assert id == to_string(user.id) end - test "locked accounts", %{conn: conn} do + test "locked accounts" do user = insert(:user, default_scope: "private") + %{conn: conn} = oauth_access(["read:accounts"], user: user) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/verify_credentials") + conn = get(conn, "/api/v1/accounts/verify_credentials") assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) assert id == to_string(user.id) @@ -904,15 +792,13 @@ test "locked accounts", %{conn: conn} do end describe "user relationships" do - test "returns the relationships for the current user", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) + setup do: oauth_access(["read:follows"]) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]}) + test "returns the relationships for the current user", %{user: user, conn: conn} do + other_user = insert(:user) + {:ok, _user} = User.follow(user, other_user) + + conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]}) assert [relationship] = json_response(conn, 200) @@ -920,34 +806,26 @@ test "returns the relationships for the current user", %{conn: conn} do end test "returns an empty list on a bad request", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/accounts/relationships", %{}) + conn = get(conn, "/api/v1/accounts/relationships", %{}) assert [] = json_response(conn, 200) end end - test "getting a list of mutes", %{conn: conn} do - user = insert(:user) + test "getting a list of mutes" do + %{user: user, conn: conn} = oauth_access(["read:mutes"]) other_user = insert(:user) {:ok, _user_relationships} = User.mute(user, other_user) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/mutes") + conn = get(conn, "/api/v1/mutes") other_user_id = to_string(other_user.id) assert [%{"id" => ^other_user_id}] = json_response(conn, 200) end - test "getting a list of blocks", %{conn: conn} do - user = insert(:user) + test "getting a list of blocks" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) other_user = insert(:user) {:ok, _user_relationship} = User.block(user, other_user) diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs index 2a1223b18..4bb9781a6 100644 --- a/test/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -10,8 +10,9 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do import Pleroma.Factory - test "returns a list of conversations", %{conn: conn} do - user_one = insert(:user) + setup do: oauth_access(["read:statuses"]) + + test "returns a list of conversations", %{user: user_one, conn: conn} do user_two = insert(:user) user_three = insert(:user) @@ -33,10 +34,7 @@ test "returns a list of conversations", %{conn: conn} do "visibility" => "private" }) - res_conn = - conn - |> assign(:user, user_one) - |> get("/api/v1/conversations") + res_conn = get(conn, "/api/v1/conversations") assert response = json_response(res_conn, 200) @@ -59,8 +57,7 @@ test "returns a list of conversations", %{conn: conn} do assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 end - test "filters conversations by recipients", %{conn: conn} do - user_one = insert(:user) + test "filters conversations by recipients", %{user: user_one, conn: conn} do user_two = insert(:user) user_three = insert(:user) @@ -96,7 +93,6 @@ test "filters conversations by recipients", %{conn: conn} do [conversation1, conversation2] = conn - |> assign(:user, user_one) |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) |> json_response(200) @@ -105,15 +101,13 @@ test "filters conversations by recipients", %{conn: conn} do [conversation1] = conn - |> assign(:user, user_one) |> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) |> json_response(200) assert conversation1["last_status"]["id"] == direct3.id end - test "updates the last_status on reply", %{conn: conn} do - user_one = insert(:user) + test "updates the last_status on reply", %{user: user_one, conn: conn} do user_two = insert(:user) {:ok, direct} = @@ -131,15 +125,13 @@ test "updates the last_status on reply", %{conn: conn} do [%{"last_status" => res_last_status}] = conn - |> assign(:user, user_one) |> get("/api/v1/conversations") |> json_response(200) assert res_last_status["id"] == direct_reply.id end - test "the user marks a conversation as read", %{conn: conn} do - user_one = insert(:user) + test "the user marks a conversation as read", %{user: user_one, conn: conn} do user_two = insert(:user) {:ok, direct} = @@ -151,15 +143,21 @@ test "the user marks a conversation as read", %{conn: conn} do assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 - [%{"id" => direct_conversation_id, "unread" => true}] = - conn + user_two_conn = + build_conn() |> assign(:user, user_two) + |> assign( + :token, + insert(:oauth_token, user: user_two, scopes: ["read:statuses", "write:conversations"]) + ) + + [%{"id" => direct_conversation_id, "unread" => true}] = + user_two_conn |> get("/api/v1/conversations") |> json_response(200) %{"unread" => false} = - conn - |> assign(:user, user_two) + user_two_conn |> post("/api/v1/conversations/#{direct_conversation_id}/read") |> json_response(200) @@ -176,7 +174,6 @@ test "the user marks a conversation as read", %{conn: conn} do [%{"unread" => true}] = conn - |> assign(:user, user_one) |> get("/api/v1/conversations") |> json_response(200) @@ -195,8 +192,7 @@ test "the user marks a conversation as read", %{conn: conn} do assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 end - test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do - user_one = insert(:user) + test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do user_two = insert(:user) {:ok, direct} = @@ -205,10 +201,7 @@ test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do "visibility" => "direct" }) - res_conn = - conn - |> assign(:user, user_one) - |> get("/api/v1/statuses/#{direct.id}/context") + res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context") assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) end diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs index 25a279cdc..55de625ba 100644 --- a/test/web/mastodon_api/controllers/domain_block_controller_test.exs +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -9,31 +9,25 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do import Pleroma.Factory - test "blocking / unblocking a domain", %{conn: conn} do - user = insert(:user) + test "blocking / unblocking a domain" do + %{user: user, conn: conn} = oauth_access(["write:blocks"]) other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - assert %{} = json_response(conn, 200) + assert %{} = json_response(ret_conn, 200) user = User.get_cached_by_ap_id(user.ap_id) assert User.blocks?(user, other_user) - conn = - build_conn() - |> assign(:user, user) - |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - assert %{} = json_response(conn, 200) + assert %{} = json_response(ret_conn, 200) user = User.get_cached_by_ap_id(user.ap_id) refute User.blocks?(user, other_user) end - test "getting a list of domain blocks", %{conn: conn} do - user = insert(:user) + test "getting a list of domain blocks" do + %{user: user, conn: conn} = oauth_access(["read:blocks"]) {:ok, user} = User.block_domain(user, "bad.site") {:ok, user} = User.block_domain(user, "even.worse.site") diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 550689788..3aea17ec7 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -7,20 +7,15 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do alias Pleroma.Web.MastodonAPI.FilterView - import Pleroma.Factory - - test "creating a filter", %{conn: conn} do - user = insert(:user) + test "creating a filter" do + %{conn: conn} = oauth_access(["write:filters"]) filter = %Pleroma.Filter{ phrase: "knights", context: ["home"] } - conn = - conn - |> assign(:user, user) - |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) assert response = json_response(conn, 200) assert response["phrase"] == filter.phrase @@ -30,8 +25,8 @@ test "creating a filter", %{conn: conn} do assert response["id"] != "" end - test "fetching a list of filters", %{conn: conn} do - user = insert(:user) + test "fetching a list of filters" do + %{user: user, conn: conn} = oauth_access(["read:filters"]) query_one = %Pleroma.Filter{ user_id: user.id, @@ -52,7 +47,6 @@ test "fetching a list of filters", %{conn: conn} do response = conn - |> assign(:user, user) |> get("/api/v1/filters") |> json_response(200) @@ -64,8 +58,8 @@ test "fetching a list of filters", %{conn: conn} do ) end - test "get a filter", %{conn: conn} do - user = insert(:user) + test "get a filter" do + %{user: user, conn: conn} = oauth_access(["read:filters"]) query = %Pleroma.Filter{ user_id: user.id, @@ -76,16 +70,13 @@ test "get a filter", %{conn: conn} do {:ok, filter} = Pleroma.Filter.create(query) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/filters/#{filter.filter_id}") + conn = get(conn, "/api/v1/filters/#{filter.filter_id}") assert _response = json_response(conn, 200) end - test "update a filter", %{conn: conn} do - user = insert(:user) + test "update a filter" do + %{user: user, conn: conn} = oauth_access(["write:filters"]) query = %Pleroma.Filter{ user_id: user.id, @@ -102,9 +93,7 @@ test "update a filter", %{conn: conn} do } conn = - conn - |> assign(:user, user) - |> put("/api/v1/filters/#{query.filter_id}", %{ + put(conn, "/api/v1/filters/#{query.filter_id}", %{ phrase: new.phrase, context: new.context }) @@ -114,8 +103,8 @@ test "update a filter", %{conn: conn} do assert response["context"] == new.context end - test "delete a filter", %{conn: conn} do - user = insert(:user) + test "delete a filter" do + %{user: user, conn: conn} = oauth_access(["write:filters"]) query = %Pleroma.Filter{ user_id: user.id, @@ -126,10 +115,7 @@ test "delete a filter", %{conn: conn} do {:ok, filter} = Pleroma.Filter.create(query) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/filters/#{filter.filter_id}") + conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") assert response = json_response(conn, 200) assert response == %{} diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs index 288cd9029..6e4a76501 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -11,8 +11,13 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do import Pleroma.Factory describe "locked accounts" do - test "/api/v1/follow_requests works" do + setup do user = insert(:user, locked: true) + %{conn: conn} = oauth_access(["follow"], user: user) + %{user: user, conn: conn} + end + + test "/api/v1/follow_requests works", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity} = ActivityPub.follow(other_user, user) @@ -20,17 +25,13 @@ test "/api/v1/follow_requests works" do assert User.following?(other_user, user) == false - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/follow_requests") + conn = get(conn, "/api/v1/follow_requests") assert [relationship] = json_response(conn, 200) assert to_string(other_user.id) == relationship["id"] end - test "/api/v1/follow_requests/:id/authorize works" do - user = insert(:user, locked: true) + test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity} = ActivityPub.follow(other_user, user) @@ -41,10 +42,7 @@ test "/api/v1/follow_requests/:id/authorize works" do assert User.following?(other_user, user) == false - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follow_requests/#{other_user.id}/authorize") + conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") assert relationship = json_response(conn, 200) assert to_string(other_user.id) == relationship["id"] @@ -55,18 +53,14 @@ test "/api/v1/follow_requests/:id/authorize works" do assert User.following?(other_user, user) == true end - test "/api/v1/follow_requests/:id/reject works" do - user = insert(:user, locked: true) + test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity} = ActivityPub.follow(other_user, user) user = User.get_cached_by_id(user.id) - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/follow_requests/#{other_user.id}/reject") + conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") assert relationship = json_response(conn, 200) assert to_string(other_user.id) == relationship["id"] diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index 093506309..a6effbb69 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -9,44 +9,35 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do import Pleroma.Factory - test "creating a list", %{conn: conn} do - user = insert(:user) + test "creating a list" do + %{conn: conn} = oauth_access(["write:lists"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists", %{"title" => "cuties"}) + conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) assert %{"title" => title} = json_response(conn, 200) assert title == "cuties" end - test "renders error for invalid params", %{conn: conn} do - user = insert(:user) + test "renders error for invalid params" do + %{conn: conn} = oauth_access(["write:lists"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists", %{"title" => nil}) + conn = post(conn, "/api/v1/lists", %{"title" => nil}) assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) end - test "listing a user's lists", %{conn: conn} do - user = insert(:user) + test "listing a user's lists" do + %{conn: conn} = oauth_access(["read:lists", "write:lists"]) conn - |> assign(:user, user) |> post("/api/v1/lists", %{"title" => "cuties"}) + |> json_response(:ok) conn - |> assign(:user, user) |> post("/api/v1/lists", %{"title" => "cofe"}) + |> json_response(:ok) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists") + conn = get(conn, "/api/v1/lists") assert [ %{"id" => _, "title" => "cofe"}, @@ -54,41 +45,35 @@ test "listing a user's lists", %{conn: conn} do ] = json_response(conn, :ok) end - test "adding users to a list", %{conn: conn} do - user = insert(:user) + test "adding users to a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [other_user.follower_address] end - test "removing users from a list", %{conn: conn} do - user = insert(:user) + test "removing users from a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) assert %{} == json_response(conn, 200) %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) assert following == [third_user.follower_address] end - test "listing users in a list", %{conn: conn} do - user = insert(:user) + test "listing users in a list" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) @@ -102,8 +87,8 @@ test "listing users in a list", %{conn: conn} do assert id == to_string(other_user.id) end - test "retrieving a list", %{conn: conn} do - user = insert(:user) + test "retrieving a list" do + %{user: user, conn: conn} = oauth_access(["read:lists"]) {:ok, list} = Pleroma.List.create("name", user) conn = @@ -115,32 +100,26 @@ test "retrieving a list", %{conn: conn} do assert id == to_string(list.id) end - test "renders 404 if list is not found", %{conn: conn} do - user = insert(:user) + test "renders 404 if list is not found" do + %{conn: conn} = oauth_access(["read:lists"]) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/lists/666") + conn = get(conn, "/api/v1/lists/666") assert %{"error" => "List not found"} = json_response(conn, :not_found) end - test "renaming a list", %{conn: conn} do - user = insert(:user) + test "renaming a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) + conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) assert %{"title" => name} = json_response(conn, 200) assert name == "newname" end - test "validates title when renaming a list", %{conn: conn} do - user = insert(:user) + test "validates title when renaming a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) conn = @@ -151,14 +130,11 @@ test "validates title when renaming a list", %{conn: conn} do assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) end - test "deleting a list", %{conn: conn} do - user = insert(:user) + test "deleting a list" do + %{user: user, conn: conn} = oauth_access(["write:lists"]) {:ok, list} = Pleroma.List.create("name", user) - conn = - conn - |> assign(:user, user) - |> delete("/api/v1/lists/#{list.id}") + conn = delete(conn, "/api/v1/lists/#{list.id}") assert %{} = json_response(conn, 200) assert is_nil(Repo.get(Pleroma.List, list.id)) diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/web/mastodon_api/controllers/media_controller_test.exs index 06c6a1cb3..042511ca4 100644 --- a/test/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/web/mastodon_api/controllers/media_controller_test.exs @@ -9,23 +9,17 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - import Pleroma.Factory + setup do: oauth_access(["write:media"]) describe "media upload" do setup do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) - image = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), filename: "an_image.jpg" } - [conn: conn, image: image] + [image: image] end clear_config([:media_proxy]) @@ -49,9 +43,7 @@ test "returns uploaded image", %{conn: conn, image: image} do end describe "PUT /api/v1/media/:id" do - setup do - actor = insert(:user) - + setup %{user: actor} do file = %Plug.Upload{ content_type: "image/jpg", path: Path.absname("test/fixtures/image.jpg"), @@ -65,13 +57,12 @@ test "returns uploaded image", %{conn: conn, image: image} do description: "test-m" ) - [actor: actor, object: object] + [object: object] end - test "updates name of media", %{conn: conn, actor: actor, object: object} do + test "updates name of media", %{conn: conn, object: object} do media = conn - |> assign(:user, actor) |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) |> json_response(:ok) @@ -79,10 +70,9 @@ test "updates name of media", %{conn: conn, actor: actor, object: object} do assert refresh_record(object).data["name"] == "test-media" end - test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do + test "returns error when request is bad", %{conn: conn, object: object} do media = conn - |> assign(:user, actor) |> put("/api/v1/media/#{object.id}", %{}) |> json_response(400) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 6635ea7a2..86303f92f 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -12,8 +12,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do import Pleroma.Factory - test "list of notifications", %{conn: conn} do - user = insert(:user) + test "list of notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) @@ -34,18 +34,15 @@ test "list of notifications", %{conn: conn} do assert response == expected_response end - test "getting a single notification", %{conn: conn} do - user = insert(:user) + test "getting a single notification" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) {:ok, [notification]} = Notification.create_notifications(activity) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/notifications/#{notification.id}") + conn = get(conn, "/api/v1/notifications/#{notification.id}") expected_response = "hi "hi @#{user.nickname}"}) @@ -72,32 +69,26 @@ test "dismissing a single notification", %{conn: conn} do assert %{} = json_response(conn, 200) end - test "clearing all notifications", %{conn: conn} do - user = insert(:user) + test "clearing all notifications" do + %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"]) other_user = insert(:user) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) {:ok, [_notification]} = Notification.create_notifications(activity) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/notifications/clear") + ret_conn = post(conn, "/api/v1/notifications/clear") - assert %{} = json_response(conn, 200) + assert %{} = json_response(ret_conn, 200) - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/notifications") + ret_conn = get(conn, "/api/v1/notifications") - assert all = json_response(conn, 200) + assert all = json_response(ret_conn, 200) assert all == [] end - test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do - user = insert(:user) + test "paginates notifications using min_id, since_id, max_id, and limit" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) @@ -138,8 +129,8 @@ test "paginates notifications using min_id, since_id, max_id, and limit", %{conn end describe "exclude_visibilities" do - test "filters notifications for mentions", %{conn: conn} do - user = insert(:user) + test "filters notifications for mentions" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) {:ok, public_activity} = @@ -154,8 +145,6 @@ test "filters notifications for mentions", %{conn: conn} do {:ok, private_activity} = CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"}) - conn = assign(conn, :user, user) - conn_res = get(conn, "/api/v1/notifications", %{ exclude_visibilities: ["public", "unlisted", "private"] @@ -189,9 +178,9 @@ test "filters notifications for mentions", %{conn: conn} do assert id == public_activity.id end - test "filters notifications for Like activities", %{conn: conn} do + test "filters notifications for Like activities" do user = insert(:user) - other_user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) {:ok, public_activity} = CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) @@ -212,7 +201,6 @@ test "filters notifications for Like activities", %{conn: conn} do activity_ids = conn - |> assign(:user, other_user) |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]}) |> json_response(200) |> Enum.map(& &1["status"]["id"]) @@ -224,7 +212,6 @@ test "filters notifications for Like activities", %{conn: conn} do activity_ids = conn - |> assign(:user, other_user) |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) |> json_response(200) |> Enum.map(& &1["status"]["id"]) @@ -236,7 +223,6 @@ test "filters notifications for Like activities", %{conn: conn} do activity_ids = conn - |> assign(:user, other_user) |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]}) |> json_response(200) |> Enum.map(& &1["status"]["id"]) @@ -248,7 +234,6 @@ test "filters notifications for Like activities", %{conn: conn} do activity_ids = conn - |> assign(:user, other_user) |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]}) |> json_response(200) |> Enum.map(& &1["status"]["id"]) @@ -259,9 +244,9 @@ test "filters notifications for Like activities", %{conn: conn} do assert direct_activity.id in activity_ids end - test "filters notifications for Announce activities", %{conn: conn} do + test "filters notifications for Announce activities" do user = insert(:user) - other_user = insert(:user) + %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) {:ok, public_activity} = CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) @@ -274,7 +259,6 @@ test "filters notifications for Announce activities", %{conn: conn} do activity_ids = conn - |> assign(:user, other_user) |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) |> json_response(200) |> Enum.map(& &1["status"]["id"]) @@ -284,8 +268,8 @@ test "filters notifications for Announce activities", %{conn: conn} do end end - test "filters notifications using exclude_types", %{conn: conn} do - user = insert(:user) + test "filters notifications using exclude_types" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) other_user = insert(:user) {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) @@ -299,8 +283,6 @@ test "filters notifications using exclude_types", %{conn: conn} do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn = assign(conn, :user, user) - conn_res = get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) @@ -322,8 +304,8 @@ test "filters notifications using exclude_types", %{conn: conn} do assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) end - test "destroy multiple", %{conn: conn} do - user = insert(:user) + test "destroy multiple" do + %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"]) other_user = insert(:user) {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) @@ -336,8 +318,6 @@ test "destroy multiple", %{conn: conn} do notification3_id = get_notification_id_by_activity(activity3) notification4_id = get_notification_id_by_activity(activity4) - conn = assign(conn, :user, user) - result = conn |> get("/api/v1/notifications") @@ -348,6 +328,7 @@ test "destroy multiple", %{conn: conn} do conn2 = conn |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"])) result = conn2 @@ -372,97 +353,110 @@ test "destroy multiple", %{conn: conn} do assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result end - test "doesn't see notifications after muting user with notifications", %{conn: conn} do - user = insert(:user) + test "doesn't see notifications after muting user with notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) user2 = insert(:user) {:ok, _, _, _} = CommonAPI.follow(user, user2) {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - conn = assign(conn, :user, user) + ret_conn = get(conn, "/api/v1/notifications") - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 + assert length(json_response(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) - conn = assign(build_conn(), :user, user) conn = get(conn, "/api/v1/notifications") assert json_response(conn, 200) == [] end - test "see notifications after muting user without notifications", %{conn: conn} do - user = insert(:user) + test "see notifications after muting user without notifications" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) user2 = insert(:user) {:ok, _, _, _} = CommonAPI.follow(user, user2) {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - conn = assign(conn, :user, user) + ret_conn = get(conn, "/api/v1/notifications") - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 + assert length(json_response(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2, false) - conn = assign(build_conn(), :user, user) conn = get(conn, "/api/v1/notifications") assert length(json_response(conn, 200)) == 1 end - test "see notifications after muting user with notifications and with_muted parameter", %{ - conn: conn - } do - user = insert(:user) + test "see notifications after muting user with notifications and with_muted parameter" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) user2 = insert(:user) {:ok, _, _, _} = CommonAPI.follow(user, user2) {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - conn = assign(conn, :user, user) + ret_conn = get(conn, "/api/v1/notifications") - conn = get(conn, "/api/v1/notifications") - - assert length(json_response(conn, 200)) == 1 + assert length(json_response(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) - conn = assign(build_conn(), :user, user) conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) assert length(json_response(conn, 200)) == 1 end - test "see move notifications with `with_move` parameter", %{ - conn: conn - } do + test "see move notifications with `with_move` parameter" do old_user = insert(:user) new_user = insert(:user, also_known_as: [old_user.ap_id]) - follower = insert(:user) + %{user: follower, conn: conn} = oauth_access(["read:notifications"]) User.follow(follower, old_user) Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user) Pleroma.Tests.ObanHelpers.perform_all() - conn = - conn - |> assign(:user, follower) - |> get("/api/v1/notifications") + ret_conn = get(conn, "/api/v1/notifications") - assert json_response(conn, 200) == [] + assert json_response(ret_conn, 200) == [] - conn = - build_conn() - |> assign(:user, follower) - |> get("/api/v1/notifications", %{"with_move" => "true"}) + conn = get(conn, "/api/v1/notifications", %{"with_move" => "true"}) assert length(json_response(conn, 200)) == 1 end + describe "link headers" do + test "preserves parameters in link headers" do + %{user: user, conn: conn} = oauth_access(["read:notifications"]) + other_user = insert(:user) + + {:ok, activity1} = + CommonAPI.post(other_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "public" + }) + + {:ok, activity2} = + CommonAPI.post(other_user, %{ + "status" => "hi @#{user.nickname}", + "visibility" => "public" + }) + + notification1 = Repo.get_by(Notification, activity_id: activity1.id) + notification2 = Repo.get_by(Notification, activity_id: activity2.id) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/notifications", %{media_only: true}) + + assert [link_header] = get_resp_header(conn, "link") + assert link_header =~ ~r/media_only=true/ + assert link_header =~ ~r/min_id=#{notification2.id}/ + assert link_header =~ ~r/max_id=#{notification1.id}/ + end + end + defp get_notification_id_by_activity(%{id: id}) do Notification |> Repo.get_by(activity_id: id) diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs index 40cf3e879..5a1cea11b 100644 --- a/test/web/mastodon_api/controllers/poll_controller_test.exs +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -11,9 +11,9 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do import Pleroma.Factory describe "GET /api/v1/polls/:id" do - test "returns poll entity for object id", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["read:statuses"]) + test "returns poll entity for object id", %{user: user, conn: conn} do {:ok, activity} = CommonAPI.post(user, %{ "status" => "Pleroma does", @@ -22,10 +22,7 @@ test "returns poll entity for object id", %{conn: conn} do object = Object.normalize(activity) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/polls/#{object.id}") + conn = get(conn, "/api/v1/polls/#{object.id}") response = json_response(conn, 200) id = to_string(object.id) @@ -33,11 +30,10 @@ test "returns poll entity for object id", %{conn: conn} do end test "does not expose polls for private statuses", %{conn: conn} do - user = insert(:user) other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "Pleroma does", "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, "visibility" => "private" @@ -45,22 +41,20 @@ test "does not expose polls for private statuses", %{conn: conn} do object = Object.normalize(activity) - conn = - conn - |> assign(:user, other_user) - |> get("/api/v1/polls/#{object.id}") + conn = get(conn, "/api/v1/polls/#{object.id}") assert json_response(conn, 404) end end describe "POST /api/v1/polls/:id/votes" do + setup do: oauth_access(["write:statuses"]) + test "votes are added to the poll", %{conn: conn} do - user = insert(:user) other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "A very delicious sandwich", "poll" => %{ "options" => ["Lettuce", "Grilled Bacon", "Tomato"], @@ -71,10 +65,7 @@ test "votes are added to the poll", %{conn: conn} do object = Object.normalize(activity) - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) assert json_response(conn, 200) object = Object.get_by_id(object.id) @@ -84,9 +75,7 @@ test "votes are added to the poll", %{conn: conn} do end) end - test "author can't vote", %{conn: conn} do - user = insert(:user) - + test "author can't vote", %{user: user, conn: conn} do {:ok, activity} = CommonAPI.post(user, %{ "status" => "Am I cute?", @@ -96,7 +85,6 @@ test "author can't vote", %{conn: conn} do object = Object.normalize(activity) assert conn - |> assign(:user, user) |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) |> json_response(422) == %{"error" => "Poll's author can't vote"} @@ -106,11 +94,10 @@ test "author can't vote", %{conn: conn} do end test "does not allow multiple choices on a single-choice question", %{conn: conn} do - user = insert(:user) other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "The glass is", "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} }) @@ -118,7 +105,6 @@ test "does not allow multiple choices on a single-choice question", %{conn: conn object = Object.normalize(activity) assert conn - |> assign(:user, other_user) |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) |> json_response(422) == %{"error" => "Too many choices"} @@ -130,42 +116,32 @@ test "does not allow multiple choices on a single-choice question", %{conn: conn end test "does not allow choice index to be greater than options count", %{conn: conn} do - user = insert(:user) other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "Am I cute?", "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} }) object = Object.normalize(activity) - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) assert json_response(conn, 422) == %{"error" => "Invalid indices"} end test "returns 404 error when object is not exist", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) + conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) assert json_response(conn, 404) == %{"error" => "Record not found"} end test "returns 404 when poll is private and not available for user", %{conn: conn} do - user = insert(:user) other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "Am I cute?", "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, "visibility" => "private" @@ -173,10 +149,7 @@ test "returns 404 when poll is private and not available for user", %{conn: conn object = Object.normalize(activity) - conn = - conn - |> assign(:user, other_user) - |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) + conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) assert json_response(conn, 404) == %{"error" => "Record not found"} end diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/web/mastodon_api/controllers/report_controller_test.exs index 979ca48f3..53c132ff4 100644 --- a/test/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -9,32 +9,30 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do import Pleroma.Factory + setup do: oauth_access(["write:reports"]) + setup do - reporter = insert(:user) target_user = insert(:user) {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) - [reporter: reporter, target_user: target_user, activity: activity] + [target_user: target_user, activity: activity] end - test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do + test "submit a basic report", %{conn: conn, target_user: target_user} do assert %{"action_taken" => false, "id" => _} = conn - |> assign(:user, reporter) |> post("/api/v1/reports", %{"account_id" => target_user.id}) |> json_response(200) end test "submit a report with statuses and comment", %{ conn: conn, - reporter: reporter, target_user: target_user, activity: activity } do assert %{"action_taken" => false, "id" => _} = conn - |> assign(:user, reporter) |> post("/api/v1/reports", %{ "account_id" => target_user.id, "status_ids" => [activity.id], @@ -46,19 +44,16 @@ test "submit a report with statuses and comment", %{ test "account_id is required", %{ conn: conn, - reporter: reporter, activity: activity } do assert %{"error" => "Valid `account_id` required"} = conn - |> assign(:user, reporter) |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) |> json_response(400) end test "comment must be up to the size specified in the config", %{ conn: conn, - reporter: reporter, target_user: target_user } do max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) @@ -68,20 +63,15 @@ test "comment must be up to the size specified in the config", %{ assert ^error = conn - |> assign(:user, reporter) |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) |> json_response(400) end test "returns error when account is not exist", %{ conn: conn, - reporter: reporter, activity: activity } do - conn = - conn - |> assign(:user, reporter) - |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) + conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) assert json_response(conn, 400) == %{"error" => "Account not found"} end diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index ae5fee2bc..9666a7f2e 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -10,89 +10,69 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do import Pleroma.Factory - test "shows scheduled activities", %{conn: conn} do - user = insert(:user) + test "shows scheduled activities" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) + scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() - conn = - conn - |> assign(:user, user) - # min_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") result = json_response(conn_res, 200) assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result # since_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") result = json_response(conn_res, 200) assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result # max_id - conn_res = - conn - |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") + conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") result = json_response(conn_res, 200) assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result end - test "shows a scheduled activity", %{conn: conn} do - user = insert(:user) + test "shows a scheduled activity" do + %{user: user, conn: conn} = oauth_access(["read:statuses"]) scheduled_activity = insert(:scheduled_activity, user: user) - res_conn = - conn - |> assign(:user, user) - |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) assert scheduled_activity_id == scheduled_activity.id |> to_string() - res_conn = - conn - |> assign(:user, user) - |> get("/api/v1/scheduled_statuses/404") + res_conn = get(conn, "/api/v1/scheduled_statuses/404") assert %{"error" => "Record not found"} = json_response(res_conn, 404) end - test "updates a scheduled activity", %{conn: conn} do - user = insert(:user) + test "updates a scheduled activity" do + %{user: user, conn: conn} = oauth_access(["write:statuses"]) scheduled_activity = insert(:scheduled_activity, user: user) new_scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) res_conn = - conn - |> assign(:user, user) - |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ + put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ scheduled_at: new_scheduled_at }) assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) - res_conn = - conn - |> assign(:user, user) - |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) assert %{"error" => "Record not found"} = json_response(res_conn, 404) end - test "deletes a scheduled activity", %{conn: conn} do - user = insert(:user) + test "deletes a scheduled activity" do + %{user: user, conn: conn} = oauth_access(["write:statuses"]) scheduled_activity = insert(:scheduled_activity, user: user) res_conn = diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 34deeba47..7fedf42e5 100644 --- a/test/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -77,13 +77,11 @@ test "search", %{conn: conn} do describe ".account_search" do test "account search", %{conn: conn} do - user = insert(:user) user_two = insert(:user, %{nickname: "shp@shitposter.club"}) user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "shp"}) |> json_response(200) @@ -94,7 +92,6 @@ test "account search", %{conn: conn} do results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "2hu"}) |> json_response(200) @@ -104,11 +101,10 @@ test "account search", %{conn: conn} do end test "returns account if query contains a space", %{conn: conn} do - user = insert(:user, %{nickname: "shp@shitposter.club"}) + insert(:user, %{nickname: "shp@shitposter.club"}) results = conn - |> assign(:user, user) |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) |> json_response(200) @@ -209,6 +205,7 @@ test "search fetches remote accounts", %{conn: conn} do conn = conn |> assign(:user, user) + |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) assert results = json_response(conn, 200) diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs index 78620a873..c4118a576 100644 --- a/test/web/mastodon_api/controllers/suggestion_controller_test.exs +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -11,8 +11,9 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do import Pleroma.Factory import Tesla.Mock - setup do - user = insert(:user) + setup do: oauth_access(["read"]) + + setup %{user: user} do other_user = insert(:user) host = Config.get([Pleroma.Web.Endpoint, :url, :host]) url500 = "http://test500?#{host}&#{user.nickname}" @@ -32,31 +33,29 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do } end) - [user: user, other_user: other_user] + [other_user: other_user] end clear_config(:suggestions) - test "returns empty result when suggestions disabled", %{conn: conn, user: user} do + test "returns empty result when suggestions disabled", %{conn: conn} do Config.put([:suggestions, :enabled], false) res = conn - |> assign(:user, user) |> get("/api/v1/suggestions") |> json_response(200) assert res == [] end - test "returns error", %{conn: conn, user: user} do + test "returns error", %{conn: conn} do Config.put([:suggestions, :enabled], true) Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") assert capture_log(fn -> res = conn - |> assign(:user, user) |> get("/api/v1/suggestions") |> json_response(500) @@ -64,13 +63,12 @@ test "returns error", %{conn: conn, user: user} do end) =~ "Could not retrieve suggestions" end - test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do + test "returns suggestions", %{conn: conn, other_user: other_user} do Config.put([:suggestions, :enabled], true) Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") res = conn - |> assign(:user, user) |> get("/api/v1/suggestions") |> json_response(200) diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index dc17cc963..bb94d8e5a 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -20,31 +20,25 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do end describe "home" do - test "the home timeline", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["read:statuses"]) + + test "the home timeline", %{user: user, conn: conn} do following = insert(:user) {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/home") + ret_conn = get(conn, "/api/v1/timelines/home") - assert Enum.empty?(json_response(conn, :ok)) + assert Enum.empty?(json_response(ret_conn, :ok)) - {:ok, user} = User.follow(user, following) + {:ok, _user} = User.follow(user, following) - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/timelines/home") + conn = get(conn, "/api/v1/timelines/home") assert [%{"content" => "test"}] = json_response(conn, :ok) end - test "the home timeline when the direct messages are excluded", %{conn: conn} do - user = insert(:user) + test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) @@ -54,10 +48,7 @@ test "the home timeline when the direct messages are excluded", %{conn: conn} do {:ok, private_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]}) + conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]}) assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"]) assert public_activity.id in status_ids @@ -99,11 +90,7 @@ test "the public timeline when public is set to false", %{conn: conn} do end test "the public timeline includes only public statuses for an authenticated user" do - user = insert(:user) - - conn = - build_conn() - |> assign(:user, user) + %{user: user, conn: conn} = oauth_access(["read:statuses"]) {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"}) {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"}) @@ -134,11 +121,13 @@ test "direct timeline", %{conn: conn} do "visibility" => "private" }) - # Only direct should be visible here - res_conn = + conn_user_two = conn |> assign(:user, user_two) - |> get("api/v1/timelines/direct") + |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) + + # Only direct should be visible here + res_conn = get(conn_user_two, "api/v1/timelines/direct") [status] = json_response(res_conn, :ok) @@ -149,6 +138,7 @@ test "direct timeline", %{conn: conn} do res_conn = build_conn() |> assign(:user, user_one) + |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"])) |> get("api/v1/timelines/direct") [status] = json_response(res_conn, :ok) @@ -156,10 +146,7 @@ test "direct timeline", %{conn: conn} do assert %{"visibility" => "direct"} = status # Both should be visible here - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/home") + res_conn = get(conn_user_two, "api/v1/timelines/home") [_s1, _s2] = json_response(res_conn, :ok) @@ -172,28 +159,23 @@ test "direct timeline", %{conn: conn} do }) end) - res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/direct") + res_conn = get(conn_user_two, "api/v1/timelines/direct") statuses = json_response(res_conn, :ok) assert length(statuses) == 20 res_conn = - conn - |> assign(:user, user_two) - |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) + get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) [status] = json_response(res_conn, :ok) assert status["url"] != direct.data["id"] end - test "doesn't include DMs from blocked users", %{conn: conn} do - blocker = insert(:user) + test "doesn't include DMs from blocked users" do + %{user: blocker, conn: conn} = oauth_access(["read:statuses"]) blocked = insert(:user) - user = insert(:user) + other_user = insert(:user) {:ok, _user_relationship} = User.block(blocker, blocked) {:ok, _blocked_direct} = @@ -203,15 +185,12 @@ test "doesn't include DMs from blocked users", %{conn: conn} do }) {:ok, direct} = - CommonAPI.post(user, %{ + CommonAPI.post(other_user, %{ "status" => "Hi @#{blocker.nickname}!", "visibility" => "direct" }) - res_conn = - conn - |> assign(:user, user) - |> get("api/v1/timelines/direct") + res_conn = get(conn, "api/v1/timelines/direct") [status] = json_response(res_conn, :ok) assert status["id"] == direct.id @@ -219,26 +198,26 @@ test "doesn't include DMs from blocked users", %{conn: conn} do end describe "list" do - test "list timeline", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["read:lists"]) + + test "list timeline", %{user: user, conn: conn} do other_user = insert(:user) {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."}) {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/list/#{list.id}") + conn = get(conn, "/api/v1/timelines/list/#{list.id}") assert [%{"id" => id}] = json_response(conn, :ok) assert id == to_string(activity_two.id) end - test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do - user = insert(:user) + test "list timeline does not leak non-public statuses for unfollowed users", %{ + user: user, + conn: conn + } do other_user = insert(:user) {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) @@ -251,10 +230,7 @@ test "list timeline does not leak non-public statuses for unfollowed users", %{c {:ok, list} = Pleroma.List.create("name", user) {:ok, list} = Pleroma.List.follow(list, other_user) - conn = - conn - |> assign(:user, user) - |> get("/api/v1/timelines/list/#{list.id}") + conn = get(conn, "/api/v1/timelines/list/#{list.id}") assert [%{"id" => id}] = json_response(conn, :ok) @@ -263,6 +239,8 @@ test "list timeline does not leak non-public statuses for unfollowed users", %{c end describe "hashtag" do + setup do: oauth_access(["n/a"]) + @tag capture_log: true test "hashtag timeline", %{conn: conn} do following = insert(:user) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 42a8779c0..c1f70f9fe 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -5,69 +5,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Notification - alias Pleroma.Repo - alias Pleroma.Web.CommonAPI - - import Pleroma.Factory - import Tesla.Mock - - setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok - end - - clear_config([:rich_media, :enabled]) - - test "unimplemented follow_requests, blocks, domain blocks" do - user = insert(:user) - - ["blocks", "domain_blocks", "follow_requests"] - |> Enum.each(fn endpoint -> - conn = - build_conn() - |> assign(:user, user) - |> get("/api/v1/#{endpoint}") - - assert [] = json_response(conn, 200) - end) - end - - describe "link headers" do - test "preserves parameters in link headers", %{conn: conn} do - user = insert(:user) - other_user = insert(:user) - - {:ok, activity1} = - CommonAPI.post(other_user, %{ - "status" => "hi @#{user.nickname}", - "visibility" => "public" - }) - - {:ok, activity2} = - CommonAPI.post(other_user, %{ - "status" => "hi @#{user.nickname}", - "visibility" => "public" - }) - - notification1 = Repo.get_by(Notification, activity_id: activity1.id) - notification2 = Repo.get_by(Notification, activity_id: activity2.id) - - conn = - conn - |> assign(:user, user) - |> get("/api/v1/notifications", %{media_only: true}) - - assert [link_header] = get_resp_header(conn, "link") - assert link_header =~ ~r/media_only=true/ - assert link_header =~ ~r/min_id=#{notification2.id}/ - assert link_header =~ ~r/max_id=#{notification1.id}/ - end - end - - describe "empty_array, stubs for mastodon api" do - test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do - user = insert(:user) + describe "empty_array/2 (stubs)" do + test "GET /api/v1/accounts/:id/identity_proofs" do + %{user: user, conn: conn} = oauth_access(["n/a"]) res = conn @@ -78,12 +18,11 @@ test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do assert res == [] end - test "GET /api/v1/endorsements", %{conn: conn} do - user = insert(:user) + test "GET /api/v1/endorsements" do + %{conn: conn} = oauth_access(["read:accounts"]) res = conn - |> assign(:user, user) |> get("/api/v1/endorsements") |> json_response(200) @@ -91,11 +30,8 @@ test "GET /api/v1/endorsements", %{conn: conn} do end test "GET /api/v1/trends", %{conn: conn} do - user = insert(:user) - res = conn - |> assign(:user, user) |> get("/api/v1/trends") |> json_response(200) diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs index c809f510f..d17026a6b 100644 --- a/test/web/pleroma_api/controllers/account_controller_test.exs +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -33,7 +33,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do test "resend account confirmation email", %{conn: conn, user: user} do conn - |> assign(:user, user) |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") |> json_response(:no_content) @@ -52,14 +51,12 @@ test "resend account confirmation email", %{conn: conn, user: user} do end describe "PATCH /api/v1/pleroma/accounts/update_avatar" do - test "user avatar can be set", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) + + test "user avatar can be set", %{user: user, conn: conn} do avatar_image = File.read!("test/fixtures/avatar_data_uri") - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) + conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) user = refresh_record(user) @@ -78,13 +75,8 @@ test "user avatar can be set", %{conn: conn} do assert %{"url" => _} = json_response(conn, 200) end - test "user avatar can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) + test "user avatar can be reset", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: ""}) user = User.get_cached_by_id(user.id) @@ -95,13 +87,10 @@ test "user avatar can be reset", %{conn: conn} do end describe "PATCH /api/v1/pleroma/accounts/update_banner" do - test "can set profile banner", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) + test "can set profile banner", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) user = refresh_record(user) assert user.banner["type"] == "Image" @@ -109,13 +98,8 @@ test "can set profile banner", %{conn: conn} do assert %{"url" => _} = json_response(conn, 200) end - test "can reset profile banner", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) + test "can reset profile banner", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) user = refresh_record(user) assert user.banner == %{} @@ -125,26 +109,18 @@ test "can reset profile banner", %{conn: conn} do end describe "PATCH /api/v1/pleroma/accounts/update_background" do - test "background image can be set", %{conn: conn} do - user = insert(:user) + setup do: oauth_access(["write:accounts"]) - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image}) + test "background image can be set", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => @image}) user = refresh_record(user) assert user.background["type"] == "Image" assert %{"url" => _} = json_response(conn, 200) end - test "background image can be reset", %{conn: conn} do - user = insert(:user) - - conn = - conn - |> assign(:user, user) - |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""}) + test "background image can be reset", %{user: user, conn: conn} do + conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => ""}) user = refresh_record(user) assert user.background == %{} @@ -155,12 +131,12 @@ test "background image can be reset", %{conn: conn} do describe "getting favorites timeline of specified user" do setup do [current_user, user] = insert_pair(:user, hide_favorites: false) - [current_user: current_user, user: user] + %{user: current_user, conn: conn} = oauth_access(["read:favourites"], user: current_user) + [current_user: current_user, user: user, conn: conn] end test "returns list of statuses favorited by specified user", %{ conn: conn, - current_user: current_user, user: user } do [activity | _] = insert_pair(:note_activity) @@ -168,7 +144,6 @@ test "returns list of statuses favorited by specified user", %{ response = conn - |> assign(:user, current_user) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") |> json_response(:ok) @@ -178,23 +153,18 @@ test "returns list of statuses favorited by specified user", %{ assert like["id"] == activity.id end - test "returns favorites for specified user_id when user is not logged in", %{ - conn: conn, + test "does not return favorites for specified user_id when user is not logged in", %{ user: user } do activity = insert(:note_activity) CommonAPI.favorite(activity.id, user) - response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert length(response) == 1 + build_conn() + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(403) end test "returns favorited DM only when user is logged in and he is one of recipients", %{ - conn: conn, current_user: current_user, user: user } do @@ -206,25 +176,24 @@ test "returns favorited DM only when user is logged in and he is one of recipien CommonAPI.favorite(direct.id, user) - response = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) + for u <- [user, current_user] do + response = + build_conn() + |> assign(:user, u) + |> assign(:token, insert(:oauth_token, user: u, scopes: ["read:favourites"])) + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(:ok) - assert length(response) == 1 + assert length(response) == 1 + end - anonymous_response = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - |> json_response(:ok) - - assert Enum.empty?(anonymous_response) + build_conn() + |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + |> json_response(403) end test "does not return others' favorited DM when user is not one of recipients", %{ conn: conn, - current_user: current_user, user: user } do user_two = insert(:user) @@ -239,7 +208,6 @@ test "does not return others' favorited DM when user is not one of recipients", response = conn - |> assign(:user, current_user) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") |> json_response(:ok) @@ -248,7 +216,6 @@ test "does not return others' favorited DM when user is not one of recipients", test "paginates favorites using since_id and max_id", %{ conn: conn, - current_user: current_user, user: user } do activities = insert_list(10, :note_activity) @@ -262,7 +229,6 @@ test "paginates favorites using since_id and max_id", %{ response = conn - |> assign(:user, current_user) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ since_id: third_activity.id, max_id: seventh_activity.id @@ -276,7 +242,6 @@ test "paginates favorites using since_id and max_id", %{ test "limits favorites using limit parameter", %{ conn: conn, - current_user: current_user, user: user } do 7 @@ -287,7 +252,6 @@ test "limits favorites using limit parameter", %{ response = conn - |> assign(:user, current_user) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) |> json_response(:ok) @@ -296,12 +260,10 @@ test "limits favorites using limit parameter", %{ test "returns empty response when user does not have any favorited statuses", %{ conn: conn, - current_user: current_user, user: user } do response = conn - |> assign(:user, current_user) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") |> json_response(:ok) @@ -314,79 +276,61 @@ test "returns 404 error when specified user is not exist", %{conn: conn} do assert json_response(conn, 404) == %{"error" => "Record not found"} end - test "returns 403 error when user has hidden own favorites", %{ - conn: conn, - current_user: current_user - } do + test "returns 403 error when user has hidden own favorites", %{conn: conn} do user = insert(:user, hide_favorites: true) activity = insert(:note_activity) CommonAPI.favorite(activity.id, user) - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") assert json_response(conn, 403) == %{"error" => "Can't get favorites"} end - test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do + test "hides favorites for new users by default", %{conn: conn} do user = insert(:user) activity = insert(:note_activity) CommonAPI.favorite(activity.id, user) - conn = - conn - |> assign(:user, current_user) - |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - assert user.hide_favorites + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") + assert json_response(conn, 403) == %{"error" => "Can't get favorites"} end end describe "subscribing / unsubscribing" do - test "subscribing / unsubscribing to a user", %{conn: conn} do - user = insert(:user) + test "subscribing / unsubscribing to a user" do + %{user: user, conn: conn} = oauth_access(["follow"]) subscription_target = insert(:user) - conn = + ret_conn = conn |> assign(:user, user) |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") - assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) + assert %{"id" => _id, "subscribing" => true} = json_response(ret_conn, 200) - conn = - build_conn() - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + conn = post(conn, "/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) end end describe "subscribing" do - test "returns 404 when subscription_target not found", %{conn: conn} do - user = insert(:user) + test "returns 404 when subscription_target not found" do + %{conn: conn} = oauth_access(["write:follows"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/target_id/subscribe") + conn = post(conn, "/api/v1/pleroma/accounts/target_id/subscribe") assert %{"error" => "Record not found"} = json_response(conn, 404) end end describe "unsubscribing" do - test "returns 404 when subscription_target not found", %{conn: conn} do - user = insert(:user) + test "returns 404 when subscription_target not found" do + %{conn: conn} = oauth_access(["follow"]) - conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/accounts/target_id/unsubscribe") + conn = post(conn, "/api/v1/pleroma/accounts/target_id/unsubscribe") assert %{"error" => "Record not found"} = json_response(conn, 404) end diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs index 3d3becefd..e1b484dae 100644 --- a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -39,9 +39,12 @@ test "shared & non-shared pack information in list_packs is ok" do test "listing remote packs" do admin = insert(:user, is_admin: true) - conn = build_conn() |> assign(:user, admin) + %{conn: conn} = oauth_access(["admin:write"], user: admin) - resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + resp = + build_conn() + |> get(emoji_api_path(conn, :list_packs)) + |> json_response(200) mock(fn %{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> @@ -123,7 +126,10 @@ test "downloading shared & unshared packs from another instance via download_fro admin = insert(:user, is_admin: true) - conn = build_conn() |> assign(:user, admin) + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, insert(:oauth_admin_token, user: admin, scopes: ["admin:write"])) assert (conn |> put_req_header("content-type", "application/json") @@ -168,8 +174,6 @@ test "downloading shared & unshared packs from another instance via download_fro # non-shared, downloaded from the fallback URL - conn = build_conn() |> assign(:user, admin) - assert conn |> put_req_header("content-type", "application/json") |> post( @@ -205,8 +209,12 @@ test "downloading shared & unshared packs from another instance via download_fro File.write!(pack_file, original_content) end) + admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) + {:ok, - admin: insert(:user, is_admin: true), + admin: admin, + conn: conn, pack_file: pack_file, new_data: %{ "license" => "Test license changed", @@ -217,10 +225,9 @@ test "downloading shared & unshared packs from another instance via download_fro end test "for a pack without a fallback source", ctx do - conn = build_conn() + conn = ctx[:conn] assert conn - |> assign(:user, ctx[:admin]) |> post( emoji_api_path(conn, :update_metadata, "test_pack"), %{ @@ -250,10 +257,9 @@ test "for a pack with a fallback source", ctx do "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF" ) - conn = build_conn() + conn = ctx[:conn] assert conn - |> assign(:user, ctx[:admin]) |> post( emoji_api_path(conn, :update_metadata, "test_pack"), %{ @@ -277,10 +283,9 @@ test "when the fallback source doesn't have all the files", ctx do new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") - conn = build_conn() + conn = ctx[:conn] assert (conn - |> assign(:user, ctx[:admin]) |> post( emoji_api_path(conn, :update_metadata, "test_pack"), %{ @@ -304,8 +309,7 @@ test "updating pack files" do end) admin = insert(:user, is_admin: true) - - conn = build_conn() + %{conn: conn} = oauth_access(["admin:write"], user: admin) same_name = %{ "action" => "add", @@ -319,8 +323,6 @@ test "updating pack files" do different_name = %{same_name | "shortcode" => "blank_2"} - conn = conn |> assign(:user, admin) - assert (conn |> post(emoji_api_path(conn, :update_file, "test_pack"), same_name) |> json_response(:conflict))["error"] =~ "already exists" @@ -392,8 +394,7 @@ test "creating and deleting a pack" do end) admin = insert(:user, is_admin: true) - - conn = build_conn() |> assign(:user, admin) + %{conn: conn} = oauth_access(["admin:write"], user: admin) assert conn |> put_req_header("content-type", "application/json") @@ -432,9 +433,9 @@ test "filesystem import" do refute Map.has_key?(resp, "test_pack_for_import") admin = insert(:user, is_admin: true) + %{conn: conn} = oauth_access(["admin:write"], user: admin) assert conn - |> assign(:user, admin) |> post(emoji_api_path(conn, :import_from_fs)) |> json_response(200) == ["test_pack_for_import"] @@ -449,11 +450,10 @@ test "filesystem import" do File.write!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt", emoji_txt_content) assert conn - |> assign(:user, admin) |> post(emoji_api_path(conn, :import_from_fs)) |> json_response(200) == ["test_pack_for_import"] - resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + resp = build_conn() |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) assert resp["test_pack_for_import"]["files"] == %{ "blank" => "blank.png", diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/web/pleroma_api/controllers/mascot_controller_test.exs index ae9539b04..40c33e609 100644 --- a/test/web/pleroma_api/controllers/mascot_controller_test.exs +++ b/test/web/pleroma_api/controllers/mascot_controller_test.exs @@ -7,10 +7,8 @@ defmodule Pleroma.Web.PleromaAPI.MascotControllerTest do alias Pleroma.User - import Pleroma.Factory - - test "mascot upload", %{conn: conn} do - user = insert(:user) + test "mascot upload" do + %{conn: conn} = oauth_access(["write:accounts"]) non_image_file = %Plug.Upload{ content_type: "audio/mpeg", @@ -18,12 +16,9 @@ test "mascot upload", %{conn: conn} do filename: "sound.mp3" } - conn = - conn - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) + ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => non_image_file}) - assert json_response(conn, 415) + assert json_response(ret_conn, 415) file = %Plug.Upload{ content_type: "image/jpg", @@ -31,23 +26,18 @@ test "mascot upload", %{conn: conn} do filename: "an_image.jpg" } - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) + conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) assert %{"id" => _, "type" => image} = json_response(conn, 200) end - test "mascot retrieving", %{conn: conn} do - user = insert(:user) - # When user hasn't set a mascot, we should just get pleroma tan back - conn = - conn - |> assign(:user, user) - |> get("/api/v1/pleroma/mascot") + test "mascot retrieving" do + %{user: user, conn: conn} = oauth_access(["read:accounts", "write:accounts"]) - assert %{"url" => url} = json_response(conn, 200) + # When user hasn't set a mascot, we should just get pleroma tan back + ret_conn = get(conn, "/api/v1/pleroma/mascot") + + assert %{"url" => url} = json_response(ret_conn, 200) assert url =~ "pleroma-fox-tan-smol" # When a user sets their mascot, we should get that back @@ -57,17 +47,14 @@ test "mascot retrieving", %{conn: conn} do filename: "an_image.jpg" } - conn = - build_conn() - |> assign(:user, user) - |> put("/api/v1/pleroma/mascot", %{"file" => file}) + ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) - assert json_response(conn, 200) + assert json_response(ret_conn, 200) user = User.get_cached_by_id(user.id) conn = - build_conn() + conn |> assign(:user, user) |> get("/api/v1/pleroma/mascot") diff --git a/test/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/web/pleroma_api/controllers/scrobble_controller_test.exs index 881f8012c..2242610f1 100644 --- a/test/web/pleroma_api/controllers/scrobble_controller_test.exs +++ b/test/web/pleroma_api/controllers/scrobble_controller_test.exs @@ -6,16 +6,13 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Web.CommonAPI - import Pleroma.Factory describe "POST /api/v1/pleroma/scrobble" do - test "works correctly", %{conn: conn} do - user = insert(:user) + test "works correctly" do + %{conn: conn} = oauth_access(["write"]) conn = - conn - |> assign(:user, user) - |> post("/api/v1/pleroma/scrobble", %{ + post(conn, "/api/v1/pleroma/scrobble", %{ "title" => "lain radio episode 1", "artist" => "lain", "album" => "lain radio", @@ -27,8 +24,8 @@ test "works correctly", %{conn: conn} do end describe "GET /api/v1/pleroma/accounts/:id/scrobbles" do - test "works correctly", %{conn: conn} do - user = insert(:user) + test "works correctly" do + %{user: user, conn: conn} = oauth_access(["read"]) {:ok, _activity} = CommonAPI.listen(user, %{ @@ -51,9 +48,7 @@ test "works correctly", %{conn: conn} do "album" => "lain radio" }) - conn = - conn - |> get("/api/v1/pleroma/accounts/#{user.id}/scrobbles") + conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles") result = json_response(conn, 200) From b7811dfb7b612e0f6cf1d9f2451e381d525d965b Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 19 Dec 2019 12:16:53 -0600 Subject: [PATCH 06/38] Instead allow a dedicated benchmark config --- config/benchmark.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/benchmark.exs b/config/benchmark.exs index c7ddb80e7..84c6782a2 100644 --- a/config/benchmark.exs +++ b/config/benchmark.exs @@ -83,10 +83,10 @@ config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock -if File.exists?("./config/test.secret.exs") do - import_config "test.secret.exs" +if File.exists?("./config/benchmark.secret.exs") do + import_config "benchmark.secret.exs" else IO.puts( - "You may want to create test.secret.exs to declare custom database connection parameters." + "You may want to create benchmark.secret.exs to declare custom database connection parameters." ) end From 7bd0bca2abadb96aa13ace36b968d57872681f7a Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 20 Dec 2019 16:33:44 +0300 Subject: [PATCH 07/38] fixed remote follow --- lib/pleroma/web/activity_pub/publisher.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 4073d3d63..0cc8fab27 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -264,6 +264,10 @@ def gather_webfinger_links(%User{} = user) do "rel" => "self", "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "href" => user.ap_id + }, + %{ + "rel" => "http://ostatus.org/schema/1.0/subscribe", + "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" } ] end From 5b8415601346447b9a66b1eabfc7538191892a76 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 20 Dec 2019 16:34:14 +0300 Subject: [PATCH 08/38] moved remote follow in separate controller --- lib/pleroma/web/router.ex | 4 +- .../twitter_api/remote_follow/follow.html.eex | 11 + .../remote_follow/follow_login.html.eex | 14 ++ .../{util => remote_follow}/followed.html.eex | 0 .../twitter_api/util/follow.html.eex | 11 - .../twitter_api/util/follow_login.html.eex | 14 -- .../controllers/remote_follow_controller.ex | 102 +++++++++ .../controllers/util_controller.ex | 91 -------- .../twitter_api/views/remote_follow_view.ex | 10 + test/web/activity_pub/publisher_test.exs | 21 ++ .../remote_follow_controller_test.exs | 211 ++++++++++++++++++ test/web/twitter_api/util_controller_test.exs | 194 +--------------- 12 files changed, 373 insertions(+), 310 deletions(-) create mode 100644 lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex create mode 100644 lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex rename lib/pleroma/web/templates/twitter_api/{util => remote_follow}/followed.html.eex (100%) delete mode 100644 lib/pleroma/web/templates/twitter_api/util/follow.html.eex delete mode 100644 lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex create mode 100644 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex create mode 100644 lib/pleroma/web/twitter_api/views/remote_follow_view.ex create mode 100644 test/web/twitter_api/remote_follow_controller_test.exs diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f6c128283..9654ab8a3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -229,9 +229,9 @@ defmodule Pleroma.Web.Router do pipe_through(:pleroma_html) post("/main/ostatus", UtilController, :remote_subscribe) - get("/ostatus_subscribe", UtilController, :remote_follow) + get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", UtilController, :do_remote_follow) + post("/ostatus_subscribe", RemoteFollowController, :do_follow) end scope "/api/pleroma", Pleroma.Web.TwitterAPI do diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex new file mode 100644 index 000000000..5ba192cd7 --- /dev/null +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex @@ -0,0 +1,11 @@ +<%= if @error == :error do %> +

Error fetching user

+<% else %> +

Remote follow

+ +

<%= @followee.nickname %>

+ <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> + <%= hidden_input f, :id, value: @followee.id %> + <%= submit "Authorize" %> + <% end %> +<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex new file mode 100644 index 000000000..df44988ee --- /dev/null +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex @@ -0,0 +1,14 @@ +<%= if @error do %> +

<%= @error %>

+<% end %> +

Log in to follow

+

<%= @followee.nickname %>

+ +<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> +<%= text_input f, :name, placeholder: "Username", required: true %> +
+<%= password_input f, :password, placeholder: "Password", required: true %> +
+<%= hidden_input f, :id, value: @followee.id %> +<%= submit "Authorize" %> +<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/util/followed.html.eex rename to lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow.html.eex deleted file mode 100644 index 06359fa6c..000000000 --- a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex +++ /dev/null @@ -1,11 +0,0 @@ -<%= if @error == :error do %> -

Error fetching user

-<% else %> -

Remote follow

- -

<%= @name %>

- <%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "user"], fn f -> %> - <%= hidden_input f, :id, value: @id %> - <%= submit "Authorize" %> - <% end %> -<% end %> diff --git a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex deleted file mode 100644 index 4e3a2be67..000000000 --- a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex +++ /dev/null @@ -1,14 +0,0 @@ -<%= if @error do %> -

<%= @error %>

-<% end %> -

Log in to follow

-

<%= @name %>

- -<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "authorization"], fn f -> %> -<%= text_input f, :name, placeholder: "Username" %> -
-<%= password_input f, :password, placeholder: "Password" %> -
-<%= hidden_input f, :id, value: @id %> -<%= submit "Authorize" %> -<% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex new file mode 100644 index 000000000..460a42566 --- /dev/null +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -0,0 +1,102 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do + use Pleroma.Web, :controller + + require Logger + + alias Pleroma.Activity + alias Pleroma.Object.Fetcher + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.User + alias Pleroma.Web.Auth.Authenticator + alias Pleroma.Web.CommonAPI + + @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action in [:do_follow]) + + # GET /ostatus_subscribe + # + def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do + case is_status?(acct) do + true -> follow_status(conn, user, acct) + _ -> follow_account(conn, user, acct) + end + end + + defp follow_status(conn, _user, acct) do + with {:ok, object} <- Fetcher.fetch_object_from_id(acct), + %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do + redirect(conn, to: "/notice/#{activity_id}") + else + error -> + handle_follow_error(conn, error) + end + end + + defp follow_account(conn, user, acct) do + with {:ok, followee} <- User.get_or_fetch(acct) do + render(conn, follow_template(user), %{error: false, followee: followee, acct: acct}) + else + {:error, _reason} -> + render(conn, follow_template(user), %{error: :error}) + end + end + + defp follow_template(%User{} = _user), do: "follow.html" + defp follow_template(_), do: "follow_login.html" + + defp is_status?(acct) do + case Fetcher.fetch_and_contain_remote_object_from_id(acct) do + {:ok, %{"type" => type}} when type in @status_types -> + true + + _ -> + false + end + end + + # POST /ostatus_subscribe + # + def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + render(conn, "followed.html", %{error: false}) + else + error -> + handle_follow_error(conn, error) + end + end + + def do_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + render(conn, "followed.html", %{error: false}) + else + error -> + handle_follow_error(conn, error) + end + end + + defp handle_follow_error(conn, {:auth, _, followee} = _) do + render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee}) + end + + defp handle_follow_error(conn, {:fetch_user, error} = _) do + Logger.debug("Remote follow failed with error #{inspect(error)}") + render(conn, "followed.html", %{error: "Could not find user"}) + end + + defp handle_follow_error(conn, {:error, "Could not follow user:" <> _} = _) do + render(conn, "followed.html", %{error: "Error following account"}) + end + + defp handle_follow_error(conn, error) do + Logger.debug("Remote follow failed with error #{inspect(error)}") + render(conn, "followed.html", %{error: "Something went wrong."}) + end +end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 799dd17ae..a61f891c7 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -7,12 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do require Logger - alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck alias Pleroma.Notification - alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.User alias Pleroma.Web @@ -77,95 +75,6 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil end end - def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do - if is_status?(acct) do - {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct) - %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"]) - redirect(conn, to: "/notice/#{activity_id}") - else - with {:ok, followee} <- User.get_or_fetch(acct) do - conn - |> render(follow_template(user), %{ - error: false, - acct: acct, - avatar: User.avatar_url(followee), - name: followee.nickname, - id: followee.id - }) - else - {:error, _reason} -> - render(conn, follow_template(user), %{error: :error}) - end - end - end - - defp follow_template(%User{} = _user), do: "follow.html" - defp follow_template(_), do: "follow_login.html" - - defp is_status?(acct) do - case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do - {:ok, %{"type" => type}} - when type in ["Article", "Event", "Note", "Video", "Page", "Question"] -> - true - - _ -> - false - end - end - - def do_remote_follow(conn, %{ - "authorization" => %{"name" => username, "password" => password, "id" => id} - }) do - with %User{} = followee <- User.get_cached_by_id(id), - {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee}, - {_, true, _} <- { - :auth, - AuthenticationPlug.checkpw(password, user.password_hash), - followee - }, - {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do - conn - |> render("followed.html", %{error: false}) - else - # Was already following user - {:error, "Could not follow user:" <> _rest} -> - render(conn, "followed.html", %{error: "Error following account"}) - - {:auth, _, followee} -> - conn - |> render("follow_login.html", %{ - error: "Wrong username or password", - id: id, - name: followee.nickname, - avatar: User.avatar_url(followee) - }) - - e -> - Logger.debug("Remote follow failed with error #{inspect(e)}") - render(conn, "followed.html", %{error: "Something went wrong."}) - end - end - - def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, - {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do - conn - |> render("followed.html", %{error: false}) - else - # Was already following user - {:error, "Could not follow user:" <> _rest} -> - render(conn, "followed.html", %{error: "Error following account"}) - - {:fetch_user, error} -> - Logger.debug("Remote follow failed with error #{inspect(error)}") - render(conn, "followed.html", %{error: "Could not find user"}) - - e -> - Logger.debug("Remote follow failed with error #{inspect(e)}") - render(conn, "followed.html", %{error: "Something went wrong."}) - end - end - def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do with {:ok, _} <- Notification.read_one(user, notification_id) do json(conn, %{status: "success"}) diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex new file mode 100644 index 000000000..8f1f21bce --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do + use Pleroma.Web, :view + import Phoenix.HTML.Form + + def avatar_url(user), do: Pleroma.User.avatar_url(user) +end diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs index e885e5a5a..015af19ab 100644 --- a/test/web/activity_pub/publisher_test.exs +++ b/test/web/activity_pub/publisher_test.exs @@ -23,6 +23,27 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do :ok end + describe "gather_webfinger_links/1" do + test "it returns links" do + user = insert(:user) + + expected_links = [ + %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"}, + %{ + "href" => user.ap_id, + "rel" => "self", + "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + }, + %{ + "rel" => "http://ostatus.org/schema/1.0/subscribe", + "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" + } + ] + + assert expected_links == Publisher.gather_webfinger_links(user) + end + end + describe "determine_inbox/2" do test "it returns sharedInbox for messages involving as:Public in to" do user = diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs new file mode 100644 index 000000000..a828253b2 --- /dev/null +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -0,0 +1,211 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.User + alias Pleroma.Web.CommonAPI + import ExUnit.CaptureLog + import Pleroma.Factory + + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + clear_config([:instance]) + clear_config([:frontend_configurations, :pleroma_fe]) + clear_config([:user, :deny_follow_blocked]) + + describe "GET /ostatus_subscribe - remote_follow/2" do + test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do + conn = + get( + conn, + "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" + ) + + assert redirected_to(conn) =~ "/notice/" + end + + test "show follow account page if the `acct` is a account link", %{conn: conn} do + response = + conn + |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") + |> html_response(200) + + assert response =~ "Log in to follow" + end + + test "show follow page if the `acct` is a account link", %{conn: conn} do + user = insert(:user) + + response = + conn + |> assign(:user, user) + |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") + |> html_response(200) + + assert response =~ "Remote follow" + end + + test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do + user = insert(:user) + + assert capture_log(fn -> + response = + conn + |> assign(:user, user) + |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found") + + assert html_response(response, 200) =~ "Error fetching user" + end) =~ "Object has been deleted" + end + end + + describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do + test "follows user", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + response = + conn + |> assign(:user, user) + |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Account followed!" + assert user2.follower_address in User.following(user) + end + + test "returns error when user is deactivated", %{conn: conn} do + user = insert(:user, deactivated: true) + user2 = insert(:user) + + response = + conn + |> assign(:user, user) + |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when user is blocked", %{conn: conn} do + Pleroma.Config.put([:user, :deny_follow_blocked], true) + user = insert(:user) + user2 = insert(:user) + + {:ok, _user_block} = Pleroma.User.block(user2, user) + + response = + conn + |> assign(:user, user) + |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when followee not found", %{conn: conn} do + user = insert(:user) + + response = + conn + |> assign(:user, user) + |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns success result when user already in followers", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + {:ok, _, _, _} = CommonAPI.follow(user, user2) + + response = + conn + |> assign(:user, refresh_record(user)) + |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Account followed!" + end + end + + describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do + test "follows", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + response = + conn + |> post("/ostatus_subscribe", %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Account followed!" + assert user2.follower_address in User.following(user) + end + + test "returns error when followee not found", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/ostatus_subscribe", %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} + }) + |> response(200) + + assert response =~ "Error following account" + end + + test "returns error when login invalid", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/ostatus_subscribe", %{ + "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} + }) + |> response(200) + + assert response =~ "Wrong username or password" + end + + test "returns error when password invalid", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + + response = + conn + |> post("/ostatus_subscribe", %{ + "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Wrong username or password" + end + + test "returns error when user is blocked", %{conn: conn} do + Pleroma.Config.put([:user, :deny_follow_blocked], true) + user = insert(:user) + user2 = insert(:user) + {:ok, _user_block} = Pleroma.User.block(user2, user) + + response = + conn + |> post("/ostatus_subscribe", %{ + "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} + }) + |> response(200) + + assert response =~ "Error following account" + end + end +end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 43299e147..e65b251df 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -9,8 +9,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User - alias Pleroma.Web.CommonAPI - import ExUnit.CaptureLog + # alias Pleroma.Web.CommonAPI + # import ExUnit.CaptureLog import Pleroma.Factory import Mock @@ -328,196 +328,6 @@ test "returns json with custom emoji with tags", %{conn: conn} do end end - describe "GET /ostatus_subscribe - remote_follow/2" do - test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do - conn = - get( - conn, - "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" - ) - - assert redirected_to(conn) =~ "/notice/" - end - - test "show follow account page if the `acct` is a account link", %{conn: conn} do - response = - get( - conn, - "/ostatus_subscribe?acct=https://mastodon.social/users/emelie" - ) - - assert html_response(response, 200) =~ "Log in to follow" - end - - test "show follow page if the `acct` is a account link", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") - - assert html_response(response, 200) =~ "Remote follow" - end - - test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do - user = insert(:user) - - assert capture_log(fn -> - response = - conn - |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found") - - assert html_response(response, 200) =~ "Error fetching user" - end) =~ "Object has been deleted" - end - end - - describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do - test "follows user", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Account followed!" - assert user2.follower_address in User.following(user) - end - - test "returns error when user is deactivated", %{conn: conn} do - user = insert(:user, deactivated: true) - user2 = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when user is blocked", %{conn: conn} do - Pleroma.Config.put([:user, :deny_follow_blocked], true) - user = insert(:user) - user2 = insert(:user) - - {:ok, _user_block} = Pleroma.User.block(user2, user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when followee not found", %{conn: conn} do - user = insert(:user) - - response = - conn - |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns success result when user already in followers", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - {:ok, _, _, _} = CommonAPI.follow(user, user2) - - response = - conn - |> assign(:user, refresh_record(user)) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) - |> response(200) - - assert response =~ "Account followed!" - end - end - - describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do - test "follows", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Account followed!" - assert user2.follower_address in User.following(user) - end - - test "returns error when followee not found", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} - }) - |> response(200) - - assert response =~ "Error following account" - end - - test "returns error when login invalid", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} - }) - |> response(200) - - assert response =~ "Wrong username or password" - end - - test "returns error when password invalid", %{conn: conn} do - user = insert(:user) - user2 = insert(:user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Wrong username or password" - end - - test "returns error when user is blocked", %{conn: conn} do - Pleroma.Config.put([:user, :deny_follow_blocked], true) - user = insert(:user) - user2 = insert(:user) - {:ok, _user_block} = Pleroma.User.block(user2, user) - - response = - conn - |> post("/ostatus_subscribe", %{ - "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} - }) - |> response(200) - - assert response =~ "Error following account" - end - end - describe "GET /api/pleroma/healthcheck" do clear_config([:instance, :healthcheck]) From c9a44ec4a6f7b98145e2b192519dfa6933f430d0 Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 22 Dec 2019 17:58:45 +0000 Subject: [PATCH 09/38] Apply suggestion to lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex --- .../web/twitter_api/controllers/remote_follow_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 460a42566..e5e52a7e8 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -30,7 +30,7 @@ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do defp follow_status(conn, _user, acct) do with {:ok, object} <- Fetcher.fetch_object_from_id(acct), %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do - redirect(conn, to: "/notice/#{activity_id}") + redirect(conn, to: o_status_path(conn, :notice, activity_id)) else error -> handle_follow_error(conn, error) From 4c505bc615b0e698db4f6d16c3b1f0b159f30e02 Mon Sep 17 00:00:00 2001 From: Maksim Date: Sun, 22 Dec 2019 17:58:54 +0000 Subject: [PATCH 10/38] Apply suggestion to lib/pleroma/web/twitter_api/views/remote_follow_view.ex --- lib/pleroma/web/twitter_api/views/remote_follow_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex index 8f1f21bce..fb6109906 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -6,5 +6,5 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do use Pleroma.Web, :view import Phoenix.HTML.Form - def avatar_url(user), do: Pleroma.User.avatar_url(user) + defdelegate avatar_url(user), to: Pleroma.User.avatar_url end From bdd71669da43698716be6494528b6e1813d0cd3d Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Sun, 22 Dec 2019 21:17:19 +0300 Subject: [PATCH 11/38] update test --- .../twitter_api/views/remote_follow_view.ex | 2 +- .../remote_follow_controller_test.exs | 47 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex index fb6109906..d469c4726 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex @@ -6,5 +6,5 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do use Pleroma.Web, :view import Phoenix.HTML.Form - defdelegate avatar_url(user), to: Pleroma.User.avatar_url + defdelegate avatar_url(user), to: Pleroma.User end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index a828253b2..3f26a889d 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -21,19 +21,19 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do describe "GET /ostatus_subscribe - remote_follow/2" do test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do - conn = - get( - conn, - "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" - ) - - assert redirected_to(conn) =~ "/notice/" + assert conn + |> get( + remote_follow_path(conn, :follow, %{ + acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" + }) + ) + |> redirected_to() =~ "/notice/" end test "show follow account page if the `acct` is a account link", %{conn: conn} do response = conn - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") + |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) |> html_response(200) assert response =~ "Log in to follow" @@ -45,7 +45,7 @@ test "show follow page if the `acct` is a account link", %{conn: conn} do response = conn |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/emelie") + |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) |> html_response(200) assert response =~ "Remote follow" @@ -58,9 +58,14 @@ test "show follow page with error when user cannot fecth by `acct` link", %{conn response = conn |> assign(:user, user) - |> get("/ostatus_subscribe?acct=https://mastodon.social/users/not_found") + |> get( + remote_follow_path(conn, :follow, %{ + acct: "https://mastodon.social/users/not_found" + }) + ) + |> html_response(200) - assert html_response(response, 200) =~ "Error fetching user" + assert response =~ "Error fetching user" end) =~ "Object has been deleted" end end @@ -73,7 +78,7 @@ test "follows user", %{conn: conn} do response = conn |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Account followed!" @@ -87,7 +92,7 @@ test "returns error when user is deactivated", %{conn: conn} do response = conn |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -103,7 +108,7 @@ test "returns error when user is blocked", %{conn: conn} do response = conn |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -115,7 +120,7 @@ test "returns error when followee not found", %{conn: conn} do response = conn |> assign(:user, user) - |> post("/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) |> response(200) assert response =~ "Error following account" @@ -129,7 +134,7 @@ test "returns success result when user already in followers", %{conn: conn} do response = conn |> assign(:user, refresh_record(user)) - |> post("/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Account followed!" @@ -143,7 +148,7 @@ test "follows", %{conn: conn} do response = conn - |> post("/ostatus_subscribe", %{ + |> post(remote_follow_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -157,7 +162,7 @@ test "returns error when followee not found", %{conn: conn} do response = conn - |> post("/ostatus_subscribe", %{ + |> post(remote_follow_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} }) |> response(200) @@ -170,7 +175,7 @@ test "returns error when login invalid", %{conn: conn} do response = conn - |> post("/ostatus_subscribe", %{ + |> post(remote_follow_path(conn, :do_follow), %{ "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} }) |> response(200) @@ -184,7 +189,7 @@ test "returns error when password invalid", %{conn: conn} do response = conn - |> post("/ostatus_subscribe", %{ + |> post(remote_follow_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} }) |> response(200) @@ -200,7 +205,7 @@ test "returns error when user is blocked", %{conn: conn} do response = conn - |> post("/ostatus_subscribe", %{ + |> post(remote_follow_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) From 933dc120438d14502e4bc4c29db904114fb6e438 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 25 Dec 2019 15:12:43 +0300 Subject: [PATCH 12/38] added code of mr#2067 --- .../controllers/remote_follow_controller.ex | 28 +++++++++++++------ .../remote_follow_controller_test.exs | 21 ++++++++++++-- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index e5e52a7e8..e0d4d5632 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -16,7 +16,12 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] - plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action in [:do_follow]) + # Note: follower can submit the form (with password auth) not being signed in (having no token) + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} + when action in [:do_follow] + ) # GET /ostatus_subscribe # @@ -61,6 +66,16 @@ defp is_status?(acct) do # POST /ostatus_subscribe # + def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do + with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, + {:ok, _, _, _} <- CommonAPI.follow(user, followee) do + render(conn, "followed.html", %{error: false}) + else + error -> + handle_follow_error(conn, error) + end + end + def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee}, @@ -72,14 +87,9 @@ def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => end end - def do_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)}, - {:ok, _, _, _} <- CommonAPI.follow(user, followee) do - render(conn, "followed.html", %{error: false}) - else - error -> - handle_follow_error(conn, error) - end + def do_follow(%{assigns: %{user: nil}} = conn, _) do + Logger.debug("Insufficient permissions: follow | write:follows.") + render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."}) end defp handle_follow_error(conn, {:auth, _, followee} = _) do diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index 3f26a889d..dd2f00dfe 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -70,7 +70,24 @@ test "show follow page with error when user cannot fecth by `acct` link", %{conn end end - describe "POST /ostatus_subscribe - do_remote_follow/2 with assigned user " do + describe "POST /ostatus_subscribe - do_follow/2 with assigned user " do + test "required `follow | write:follows` scope", %{conn: conn} do + user = insert(:user) + user2 = insert(:user) + read_token = insert(:oauth_token, user: user, scopes: ["read"]) + + assert capture_log(fn -> + response = + conn + |> assign(:user, user) + |> assign(:token, read_token) + |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> response(200) + + assert response =~ "Error following account" + end) =~ "Insufficient permissions: follow | write:follows." + end + test "follows user", %{conn: conn} do user = insert(:user) user2 = insert(:user) @@ -141,7 +158,7 @@ test "returns success result when user already in followers", %{conn: conn} do end end - describe "POST /ostatus_subscribe - do_remote_follow/2 without assigned user " do + describe "POST /ostatus_subscribe - follow/2 without assigned user " do test "follows", %{conn: conn} do user = insert(:user) user2 = insert(:user) From fa7d8e77e64abbbd488152d8063fe9d012c8ac06 Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Fri, 3 Jan 2020 16:21:52 +0300 Subject: [PATCH 13/38] fixed Metadata.Utils.scrub_html_and_truncate --- lib/pleroma/web/metadata/utils.ex | 2 ++ test/web/metadata/utils_test.exs | 32 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 test/web/metadata/utils_test.exs diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 382ecf426..589d11901 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -15,6 +15,7 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do |> String.replace(~r//, " ") |> HTML.get_cached_stripped_html_for_activity(object, "metadata") |> Emoji.Formatter.demojify() + |> HtmlEntities.decode() |> Formatter.truncate() end @@ -25,6 +26,7 @@ def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) |> String.replace(~r//, " ") |> HTML.strip_tags() |> Emoji.Formatter.demojify() + |> HtmlEntities.decode() |> Formatter.truncate(max_length) end diff --git a/test/web/metadata/utils_test.exs b/test/web/metadata/utils_test.exs new file mode 100644 index 000000000..7547f2932 --- /dev/null +++ b/test/web/metadata/utils_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.UtilsTest do + use Pleroma.DataCase + import Pleroma.Factory + alias Pleroma.Web.Metadata.Utils + + describe "scrub_html_and_truncate/1" do + test "it returns text without encode HTML" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "content" => "Pleroma's really cool!" + } + }) + + assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" + end + end + + describe "scrub_html_and_truncate/2" do + test "it returns text without encode HTML" do + assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" + end + end +end From 0b6d1292d29e1f376566fe75aca60c612e9233dc Mon Sep 17 00:00:00 2001 From: eugenijm Date: Fri, 20 Dec 2019 16:38:21 +0300 Subject: [PATCH 14/38] Fix mark-as-read (`POST /api/v1/conversations/:id/read`) refreshing updated_at and bringing conversation to the top in the user's direct conversation list --- CHANGELOG.md | 1 + lib/pleroma/conversation/participation.ex | 10 ++++++---- test/conversation/participation_test.exs | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22f199b3d..efa3518e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname` - AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports) - Admin API: Error when trying to update reports in the "old" format +- Mastodon API: Marking a conversation as read (`POST /api/v1/conversations/:id/read`) brings it to the top in the user's direct conversation list ## [1.1.6] - 2019-11-19 diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index aafe57280..e5d28ebff 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -64,11 +64,13 @@ def mark_as_read(%User{} = user, %Conversation{} = conversation) do end def mark_as_read(participation) do - participation - |> read_cng(%{read: true}) - |> Repo.update() + __MODULE__ + |> where(id: ^participation.id) + |> update(set: [read: true]) + |> select([p], p) + |> Repo.update_all([]) |> case do - {:ok, participation} -> + {1, [participation]} -> participation = Repo.preload(participation, :user) User.set_unread_conversation_count(participation.user) {:ok, participation} diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs index ba81c0d4b..ab9f27b2f 100644 --- a/test/conversation/participation_test.exs +++ b/test/conversation/participation_test.exs @@ -125,9 +125,10 @@ test "recreating an existing participations sets it to unread" do test "it marks a participation as read" do participation = insert(:participation, %{read: false}) - {:ok, participation} = Participation.mark_as_read(participation) + {:ok, updated_participation} = Participation.mark_as_read(participation) - assert participation.read + assert updated_participation.read + assert updated_participation.updated_at == participation.updated_at end test "it marks a participation as unread" do From b55f2563d05f2d0ca5e1293391c67d73d60d501d Mon Sep 17 00:00:00 2001 From: RX14 Date: Sun, 5 Jan 2020 19:00:48 +0000 Subject: [PATCH 15/38] Fix SMTP mailer example `ssl: true` and `tls: :always` tries to use both TLS and STARTTLS on the same SMTP connection, causing it to fail. --- docs/configuration/cheatsheet.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index a214b6e2f..cad3af68d 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -453,6 +453,7 @@ An example for Sendgrid adapter: ```elixir config :pleroma, Pleroma.Emails.Mailer, + enabled: true, adapter: Swoosh.Adapters.Sendgrid, api_key: "YOUR_API_KEY" ``` @@ -461,13 +462,13 @@ An example for SMTP adapter: ```elixir config :pleroma, Pleroma.Emails.Mailer, + enabled: true, adapter: Swoosh.Adapters.SMTP, relay: "smtp.gmail.com", username: "YOUR_USERNAME@gmail.com", password: "YOUR_SMTP_PASSWORD", port: 465, ssl: true, - tls: :always, auth: :always ``` From 180f257ced4ace9467d1946a582a5f6f962d0163 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 6 Jan 2020 14:10:07 +0000 Subject: [PATCH 16/38] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efa3518e4..80f0d98af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,7 +104,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname` - AdminAPI: If some status received reports both in the "new" format and "old" format it was considered reports on two different statuses (in the context of grouped reports) - Admin API: Error when trying to update reports in the "old" format -- Mastodon API: Marking a conversation as read (`POST /api/v1/conversations/:id/read`) brings it to the top in the user's direct conversation list +- Mastodon API: Marking a conversation as read (`POST /api/v1/conversations/:id/read`) now no longer brings it to the top in the user's direct conversation list ## [1.1.6] - 2019-11-19 From 70410dfafd272bd1f38602446cc4f6e83645326f Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 8 Jan 2020 16:40:38 +0300 Subject: [PATCH 17/38] fix create service actor --- lib/pleroma/user.ex | 53 ++++++++++++++++++++------- lib/pleroma/web/activity_pub/relay.ex | 4 +- test/user_test.exs | 37 +++++++++++++++++++ 3 files changed, 80 insertions(+), 14 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 706aee2ff..7ce9e17df 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1430,20 +1430,47 @@ def get_or_fetch_by_ap_id(ap_id) do Creates an internal service actor by URI if missing. Optionally takes nickname for addressing. """ - def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do - with user when is_nil(user) <- get_cached_by_ap_id(uri) do - {:ok, user} = - %User{ - invisible: true, - local: true, - ap_id: uri, - nickname: nickname, - follower_address: uri <> "/followers" - } - |> Repo.insert() + @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil + def get_or_create_service_actor_by_ap_id(uri, nickname) do + {_, user} = + case get_cached_by_ap_id(uri) do + nil -> + with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do + Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}") + {:error, nil} + end - user - end + %User{invisible: false} = user -> + set_invisible(user) + + user -> + {:ok, user} + end + + user + end + + @spec set_invisible(User.t()) :: {:ok, User.t()} + defp set_invisible(user) do + user + |> change(%{invisible: true}) + |> update_and_set_cache() + end + + @spec create_service_actor(String.t(), String.t()) :: + {:ok, User.t()} | {:error, Ecto.Changeset.t()} + defp create_service_actor(uri, nickname) do + %User{ + invisible: true, + local: true, + ap_id: uri, + nickname: nickname, + follower_address: uri <> "/followers" + } + |> change + |> unique_constraint(:nickname) + |> Repo.insert() + |> set_cache() end # AP style diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index 99a804568..48a1b71e0 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -9,10 +9,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do alias Pleroma.Web.ActivityPub.ActivityPub require Logger + @relay_nickname "relay" + def get_actor do actor = relay_ap_id() - |> User.get_or_create_service_actor_by_ap_id() + |> User.get_or_create_service_actor_by_ap_id(@relay_nickname) actor end diff --git a/test/user_test.exs b/test/user_test.exs index d7ab63463..9da1e02a9 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -17,6 +17,7 @@ defmodule Pleroma.UserTest do import Mock import Pleroma.Factory + import ExUnit.CaptureLog setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -26,6 +27,42 @@ defmodule Pleroma.UserTest do clear_config([:instance, :account_activation_required]) describe "service actors" do + test "returns updated invisible actor" do + uri = "#{Pleroma.Web.Endpoint.url()}/relay" + followers_uri = "#{uri}/followers" + + insert( + :user, + %{ + nickname: "relay", + invisible: false, + local: true, + ap_id: uri, + follower_address: followers_uri + } + ) + + actor = User.get_or_create_service_actor_by_ap_id(uri, "relay") + assert actor.invisible + end + + test "returns relay user" do + uri = "#{Pleroma.Web.Endpoint.url()}/relay" + followers_uri = "#{uri}/followers" + + assert %User{ + nickname: "relay", + invisible: true, + local: true, + ap_id: ^uri, + follower_address: ^followers_uri + } = User.get_or_create_service_actor_by_ap_id(uri, "relay") + + assert capture_log(fn -> + refute User.get_or_create_service_actor_by_ap_id("/relay", "relay") + end) =~ "Cannot create service actor:" + end + test "returns invisible actor" do uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test" followers_uri = "#{uri}/followers" From fd3c23af63fa7a3642aef37cdb82561a5955310e Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 9 Jan 2020 10:00:15 -0600 Subject: [PATCH 18/38] Update AdminFE build --- .../{app.8589ec81.css => app.fdd73ce4.css} | Bin 12809 -> 12836 bytes priv/static/adminfe/chunk-0cb6.8d811a09.css | Bin 480 -> 0 bytes priv/static/adminfe/chunk-0cc4.571d0025.css | Bin 0 -> 2617 bytes ...a.6e185c68.css => chunk-15fa.2246593e.css} | Bin ...1.5bd2ca85.css => chunk-18e1.ed715f8d.css} | Bin ...b.c7882778.css => chunk-1c46.f36071a4.css} | Bin priv/static/adminfe/chunk-2943.1b6fd9a7.css | Bin 2321 -> 0 bytes priv/static/adminfe/chunk-3d1c.b2eb7234.css | Bin 723 -> 0 bytes priv/static/adminfe/chunk-4df4.e217dea0.css | Bin 3624 -> 0 bytes priv/static/adminfe/chunk-7de9.889d1da1.css | Bin 0 -> 7044 bytes ...e.b6944d38.css => chunk-7f8e.d508c376.css} | Bin ...a.062aa087.css => chunk-9bb0.2a82c722.css} | Bin 2044 -> 2044 bytes priv/static/adminfe/chunk-a601.62c86eea.css | Bin 0 -> 2784 bytes ...2.723b6cc5.css => chunk-d01a.03fe0a3f.css} | Bin 3252 -> 3252 bytes priv/static/adminfe/chunk-f3c9.155bfc51.css | Bin 0 -> 3758 bytes priv/static/adminfe/index.html | 2 +- .../static/adminfe/static/js/ZhIB.861df339.js | Bin 0 -> 11328 bytes .../adminfe/static/js/ZhIB.861df339.js.map | Bin 0 -> 49483 bytes priv/static/adminfe/static/js/app.19b7049e.js | Bin 0 -> 180590 bytes .../adminfe/static/js/app.19b7049e.js.map | Bin 0 -> 396651 bytes priv/static/adminfe/static/js/app.9c4316f1.js | Bin 167236 -> 0 bytes .../adminfe/static/js/app.9c4316f1.js.map | Bin 366548 -> 0 bytes .../adminfe/static/js/chunk-0620.c765c190.js | Bin 13030 -> 0 bytes .../static/js/chunk-0620.c765c190.js.map | Bin 63567 -> 0 bytes .../adminfe/static/js/chunk-0cb6.b9f32e0c.js | Bin 16157 -> 0 bytes .../static/js/chunk-0cb6.b9f32e0c.js.map | Bin 57112 -> 0 bytes .../adminfe/static/js/chunk-0cc4.35b47d0a.js | Bin 0 -> 26736 bytes .../static/js/chunk-0cc4.35b47d0a.js.map | Bin 0 -> 81998 bytes ...5fa.34dcb9d8.js => chunk-15fa.10871dbf.js} | Bin 7919 -> 7919 bytes ...b9d8.js.map => chunk-15fa.10871dbf.js.map} | Bin 17438 -> 17438 bytes ...8e1.f8bb78f3.js => chunk-18e1.9f7c9b0f.js} | Bin 2080 -> 2080 bytes ...78f3.js.map => chunk-18e1.9f7c9b0f.js.map} | Bin 9090 -> 9090 bytes .../adminfe/static/js/chunk-1c46.b92c7c1b.js | Bin 0 -> 7737 bytes .../static/js/chunk-1c46.b92c7c1b.js.map | Bin 0 -> 26010 bytes .../adminfe/static/js/chunk-23b2.442bb8df.js | Bin 28092 -> 0 bytes .../static/js/chunk-23b2.442bb8df.js.map | Bin 91362 -> 0 bytes .../adminfe/static/js/chunk-2943.8ab5d0d9.js | Bin 231394 -> 0 bytes .../static/js/chunk-2943.8ab5d0d9.js.map | Bin 689117 -> 0 bytes .../adminfe/static/js/chunk-3d1c.3334d3f1.js | Bin 4822 -> 0 bytes .../static/js/chunk-3d1c.3334d3f1.js.map | Bin 18519 -> 0 bytes .../adminfe/static/js/chunk-4df4.9655f394.js | Bin 17765 -> 0 bytes .../static/js/chunk-4df4.9655f394.js.map | Bin 66937 -> 0 bytes .../adminfe/static/js/chunk-538a.04530055.js | Bin 5112 -> 0 bytes .../static/js/chunk-538a.04530055.js.map | Bin 19586 -> 0 bytes .../adminfe/static/js/chunk-7c6b.5240e052.js | Bin 7947 -> 0 bytes .../static/js/chunk-7c6b.5240e052.js.map | Bin 26432 -> 0 bytes .../adminfe/static/js/chunk-7de9.7b8cda50.js | Bin 0 -> 29969 bytes .../static/js/chunk-7de9.7b8cda50.js.map | Bin 0 -> 117302 bytes ...f8e.c1eb619d.js => chunk-7f8e.2c3e63e9.js} | Bin 9618 -> 9618 bytes ...619d.js.map => chunk-7f8e.2c3e63e9.js.map} | Bin 39890 -> 39890 bytes .../adminfe/static/js/chunk-9bb0.9c56835f.js | Bin 0 -> 5112 bytes .../static/js/chunk-9bb0.9c56835f.js.map | Bin 0 -> 19744 bytes .../adminfe/static/js/chunk-a601.cc880efe.js | Bin 0 -> 15418 bytes .../static/js/chunk-a601.cc880efe.js.map | Bin 0 -> 53763 bytes .../adminfe/static/js/chunk-d01a.970cf312.js | Bin 0 -> 32170 bytes .../static/js/chunk-d01a.970cf312.js.map | Bin 0 -> 110610 bytes .../adminfe/static/js/chunk-f3c9.b3de53e2.js | Bin 0 -> 242980 bytes .../static/js/chunk-f3c9.b3de53e2.js.map | Bin 0 -> 745354 bytes .../adminfe/static/js/runtime.46db235c.js | Bin 3922 -> 0 bytes .../adminfe/static/js/runtime.46db235c.js.map | Bin 16658 -> 0 bytes .../adminfe/static/js/runtime.d6d1aaab.js | Bin 0 -> 3906 bytes .../adminfe/static/js/runtime.d6d1aaab.js.map | Bin 0 -> 16640 bytes .../static/tinymce4.7.5/langs/zh_CN.js | Bin 9928 -> 0 bytes .../plugins/codesample/css/prism.css | Bin 2334 -> 0 bytes .../plugins/emoticons/img/smiley-cool.gif | Bin 354 -> 0 bytes .../plugins/emoticons/img/smiley-cry.gif | Bin 329 -> 0 bytes .../emoticons/img/smiley-embarassed.gif | Bin 331 -> 0 bytes .../emoticons/img/smiley-foot-in-mouth.gif | Bin 342 -> 0 bytes .../plugins/emoticons/img/smiley-frown.gif | Bin 340 -> 0 bytes .../plugins/emoticons/img/smiley-innocent.gif | Bin 336 -> 0 bytes .../plugins/emoticons/img/smiley-kiss.gif | Bin 338 -> 0 bytes .../plugins/emoticons/img/smiley-laughing.gif | Bin 343 -> 0 bytes .../emoticons/img/smiley-money-mouth.gif | Bin 321 -> 0 bytes .../plugins/emoticons/img/smiley-sealed.gif | Bin 323 -> 0 bytes .../plugins/emoticons/img/smiley-smile.gif | Bin 344 -> 0 bytes .../emoticons/img/smiley-surprised.gif | Bin 338 -> 0 bytes .../emoticons/img/smiley-tongue-out.gif | Bin 328 -> 0 bytes .../emoticons/img/smiley-undecided.gif | Bin 337 -> 0 bytes .../plugins/emoticons/img/smiley-wink.gif | Bin 350 -> 0 bytes .../plugins/emoticons/img/smiley-yell.gif | Bin 336 -> 0 bytes .../plugins/visualblocks/css/visualblocks.css | Bin 5473 -> 0 bytes .../skins/lightgray/content.inline.min.css | Bin 3326 -> 0 bytes .../skins/lightgray/content.min.css | Bin 3753 -> 0 bytes .../skins/lightgray/fonts/tinymce-mobile.woff | Bin 4624 -> 0 bytes .../skins/lightgray/fonts/tinymce-small.eot | Bin 9492 -> 0 bytes .../skins/lightgray/fonts/tinymce-small.svg | 63 --------- .../skins/lightgray/fonts/tinymce-small.ttf | Bin 9304 -> 0 bytes .../skins/lightgray/fonts/tinymce-small.woff | Bin 9380 -> 0 bytes .../skins/lightgray/fonts/tinymce.eot | Bin 18808 -> 0 bytes .../skins/lightgray/fonts/tinymce.svg | 131 ------------------ .../skins/lightgray/fonts/tinymce.ttf | Bin 18644 -> 0 bytes .../skins/lightgray/fonts/tinymce.woff | Bin 18720 -> 0 bytes .../skins/lightgray/img/anchor.gif | Bin 53 -> 0 bytes .../skins/lightgray/img/loader.gif | Bin 2608 -> 0 bytes .../skins/lightgray/img/object.gif | Bin 152 -> 0 bytes .../skins/lightgray/img/trans.gif | Bin 43 -> 0 bytes .../tinymce4.7.5/skins/lightgray/skin.min.css | Bin 43307 -> 0 bytes .../skins/lightgray/skin.min.css.map | 1 - .../static/tinymce4.7.5/tinymce.min.js | Bin 834083 -> 0 bytes 99 files changed, 1 insertion(+), 196 deletions(-) rename priv/static/adminfe/{app.8589ec81.css => app.fdd73ce4.css} (50%) delete mode 100644 priv/static/adminfe/chunk-0cb6.8d811a09.css create mode 100644 priv/static/adminfe/chunk-0cc4.571d0025.css rename priv/static/adminfe/{chunk-15fa.6e185c68.css => chunk-15fa.2246593e.css} (100%) rename priv/static/adminfe/{chunk-18e1.5bd2ca85.css => chunk-18e1.ed715f8d.css} (100%) rename priv/static/adminfe/{chunk-7c6b.c7882778.css => chunk-1c46.f36071a4.css} (100%) delete mode 100644 priv/static/adminfe/chunk-2943.1b6fd9a7.css delete mode 100644 priv/static/adminfe/chunk-3d1c.b2eb7234.css delete mode 100644 priv/static/adminfe/chunk-4df4.e217dea0.css create mode 100644 priv/static/adminfe/chunk-7de9.889d1da1.css rename priv/static/adminfe/{chunk-7f8e.b6944d38.css => chunk-7f8e.d508c376.css} (100%) rename priv/static/adminfe/{chunk-538a.062aa087.css => chunk-9bb0.2a82c722.css} (62%) create mode 100644 priv/static/adminfe/chunk-a601.62c86eea.css rename priv/static/adminfe/{chunk-23b2.723b6cc5.css => chunk-d01a.03fe0a3f.css} (86%) create mode 100644 priv/static/adminfe/chunk-f3c9.155bfc51.css create mode 100644 priv/static/adminfe/static/js/ZhIB.861df339.js create mode 100644 priv/static/adminfe/static/js/ZhIB.861df339.js.map create mode 100644 priv/static/adminfe/static/js/app.19b7049e.js create mode 100644 priv/static/adminfe/static/js/app.19b7049e.js.map delete mode 100644 priv/static/adminfe/static/js/app.9c4316f1.js delete mode 100644 priv/static/adminfe/static/js/app.9c4316f1.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-0620.c765c190.js delete mode 100644 priv/static/adminfe/static/js/chunk-0620.c765c190.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-0cb6.b9f32e0c.js delete mode 100644 priv/static/adminfe/static/js/chunk-0cb6.b9f32e0c.js.map create mode 100644 priv/static/adminfe/static/js/chunk-0cc4.35b47d0a.js create mode 100644 priv/static/adminfe/static/js/chunk-0cc4.35b47d0a.js.map rename priv/static/adminfe/static/js/{chunk-15fa.34dcb9d8.js => chunk-15fa.10871dbf.js} (99%) rename priv/static/adminfe/static/js/{chunk-15fa.34dcb9d8.js.map => chunk-15fa.10871dbf.js.map} (99%) rename priv/static/adminfe/static/js/{chunk-18e1.f8bb78f3.js => chunk-18e1.9f7c9b0f.js} (97%) rename priv/static/adminfe/static/js/{chunk-18e1.f8bb78f3.js.map => chunk-18e1.9f7c9b0f.js.map} (98%) create mode 100644 priv/static/adminfe/static/js/chunk-1c46.b92c7c1b.js create mode 100644 priv/static/adminfe/static/js/chunk-1c46.b92c7c1b.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-23b2.442bb8df.js delete mode 100644 priv/static/adminfe/static/js/chunk-23b2.442bb8df.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-2943.8ab5d0d9.js delete mode 100644 priv/static/adminfe/static/js/chunk-2943.8ab5d0d9.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-3d1c.3334d3f1.js delete mode 100644 priv/static/adminfe/static/js/chunk-3d1c.3334d3f1.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-4df4.9655f394.js delete mode 100644 priv/static/adminfe/static/js/chunk-4df4.9655f394.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-538a.04530055.js delete mode 100644 priv/static/adminfe/static/js/chunk-538a.04530055.js.map delete mode 100644 priv/static/adminfe/static/js/chunk-7c6b.5240e052.js delete mode 100644 priv/static/adminfe/static/js/chunk-7c6b.5240e052.js.map create mode 100644 priv/static/adminfe/static/js/chunk-7de9.7b8cda50.js create mode 100644 priv/static/adminfe/static/js/chunk-7de9.7b8cda50.js.map rename priv/static/adminfe/static/js/{chunk-7f8e.c1eb619d.js => chunk-7f8e.2c3e63e9.js} (99%) rename priv/static/adminfe/static/js/{chunk-7f8e.c1eb619d.js.map => chunk-7f8e.2c3e63e9.js.map} (99%) create mode 100644 priv/static/adminfe/static/js/chunk-9bb0.9c56835f.js create mode 100644 priv/static/adminfe/static/js/chunk-9bb0.9c56835f.js.map create mode 100644 priv/static/adminfe/static/js/chunk-a601.cc880efe.js create mode 100644 priv/static/adminfe/static/js/chunk-a601.cc880efe.js.map create mode 100644 priv/static/adminfe/static/js/chunk-d01a.970cf312.js create mode 100644 priv/static/adminfe/static/js/chunk-d01a.970cf312.js.map create mode 100644 priv/static/adminfe/static/js/chunk-f3c9.b3de53e2.js create mode 100644 priv/static/adminfe/static/js/chunk-f3c9.b3de53e2.js.map delete mode 100644 priv/static/adminfe/static/js/runtime.46db235c.js delete mode 100644 priv/static/adminfe/static/js/runtime.46db235c.js.map create mode 100644 priv/static/adminfe/static/js/runtime.d6d1aaab.js create mode 100644 priv/static/adminfe/static/js/runtime.d6d1aaab.js.map delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/langs/zh_CN.js delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/codesample/css/prism.css delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-cool.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-cry.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-embarassed.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-foot-in-mouth.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-frown.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-innocent.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-kiss.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-laughing.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-money-mouth.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-sealed.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-smile.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-surprised.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-tongue-out.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-undecided.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-wink.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/emoticons/img/smiley-yell.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/plugins/visualblocks/css/visualblocks.css delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/content.inline.min.css delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/content.min.css delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-mobile.woff delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.eot delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.svg delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.ttf delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce-small.woff delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.eot delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.svg delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.ttf delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/fonts/tinymce.woff delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/img/anchor.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/img/loader.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/img/object.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/img/trans.gif delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/skin.min.css delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/skins/lightgray/skin.min.css.map delete mode 100644 priv/static/adminfe/static/tinymce4.7.5/tinymce.min.js diff --git a/priv/static/adminfe/app.8589ec81.css b/priv/static/adminfe/app.fdd73ce4.css similarity index 50% rename from priv/static/adminfe/app.8589ec81.css rename to priv/static/adminfe/app.fdd73ce4.css index b82fcc39e855a2a8b6907b9c8ffd69ea0651eab3..473ec1b86bbe0189960fea70c7be503c65500663 100644 GIT binary patch delta 428 zcmeB7S(36LSW?Aqp+Ff=zbw@6D%p1jM@9{?`3 BcE|t# delta 398 zcmZ3I(wVX$SaNfolt_;OyJ#b{uBrsg`ERCPteT zHG~-1p}ffs$`X@ U_YE_LNvgS_L6W7}e=Af>wh^!hs}tYtxQw%bX9(NXX(5S-bcLHg!$Y%SoQuAc$T6y2UT6!j{1zHL{uV4P*FVD@jCjbBd diff --git a/priv/static/adminfe/chunk-0cc4.571d0025.css b/priv/static/adminfe/chunk-0cc4.571d0025.css new file mode 100644 index 0000000000000000000000000000000000000000..8bd6a2e50292892916d3bf1b1e434b1e4bbbcd60 GIT binary patch literal 2617 zcmd5;+m4$s5d9UTs!~^~Bf>V>m3irZRMpC01MY&2%#cf?{Cmg7CJ@+%Zd<7@B=(HQ zb2)Ry894h*_i`!G$GR~zc z=@u8)&D;{nwE`xUFiV?Uc5ThhlpIC&DuHDBl#Y8%M~YgUrh9gsPLxT@@>lw!??4vk zl=u_#T`zTR2nDpmGlFCSjzXyGM%08G-Ew_giMgmX18QVhfzy;23lUtxGL(jyQYzwf z>CUDBSovSaK1<|K*c=li%q@|#UAG_-MAOkhafs0ZW0?;R37?R}0Yhn0>B9;TsN25e zy4Hq%*vI>1zfUu)ZE19`IHu7FilRvK-gKzMdeGNR2Mqd-g8ryiG(pCCM z8f_45uBR>@Bes18h|KQuix9@x2k-5TS@Fi+nC-5Wbk(N=vVkEs+TfN{E}L1c^H2>*bsOe1?Y)TV!3 znTuM0;Ql5!*Mdqw9OS957U-5Po5A~835Lz|=NbNU-VTX#t?w5{36<_4Q2jE->(>U^ zn?;`*q-rh>5+}jX;7|MK_Vf@j#ZkzxY*XPjPQIY@vsU#v>T-jF^Nye z*IDMUcMQU3OAR-P9dn*FoMaXXs#!XFdGV>9j@D@Vv?wd;j3cl zavM!|(0LO%^RYg?@y(mui{hPV32k*Wtflrd^wYRpkM3^)^q*2da9!Z&3Xh{pD0Cc6 zn|G=bjq_Q*Oi-vc?HMG3zxfWje#dXRH|7Zy7#Z+^8IVupUHlI(hQ>fnRMy K-JQW0LG}v*v(j+@ literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/chunk-15fa.6e185c68.css b/priv/static/adminfe/chunk-15fa.2246593e.css similarity index 100% rename from priv/static/adminfe/chunk-15fa.6e185c68.css rename to priv/static/adminfe/chunk-15fa.2246593e.css diff --git a/priv/static/adminfe/chunk-18e1.5bd2ca85.css b/priv/static/adminfe/chunk-18e1.ed715f8d.css similarity index 100% rename from priv/static/adminfe/chunk-18e1.5bd2ca85.css rename to priv/static/adminfe/chunk-18e1.ed715f8d.css diff --git a/priv/static/adminfe/chunk-7c6b.c7882778.css b/priv/static/adminfe/chunk-1c46.f36071a4.css similarity index 100% rename from priv/static/adminfe/chunk-7c6b.c7882778.css rename to priv/static/adminfe/chunk-1c46.f36071a4.css diff --git a/priv/static/adminfe/chunk-2943.1b6fd9a7.css b/priv/static/adminfe/chunk-2943.1b6fd9a7.css deleted file mode 100644 index 0c9284744e242456ea5327b6682c1ec4f8abe280..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2321 zcmd6pTWjM+6oCJVAS}e-5t8lLS?Q&;u$z~b!akK{F(c_%9x|E{GviAt{NH=#B1?*s z-7WN?J7Km=xJTRG>H$aEVJtbz zsv*ok(=}ApOc2HDy~&nI_^yFNX}S}u%sxw#^9d7k*S=_rtvt#SAnL1dAC%(~&~n%rWN69yh?!^)J=@jehaL(7i7c2$FDYPzKMxR-Q1dKB zK`l9}qB{TxCNNcG(PS3&C2puLr5S+<@dTk1eO-loNK=8$=)0f|j4WMo{u{(Uus#XF zx1)#Ve28k7()09Vnk=yUW^*a{OQcH$AO^`^ zAHmJK?_3Jc5if}=({{^*UA@Dham!_VXtrBU3*ecM<~|mZ7}*lvE3;mWxZ2vg5$yCe zvsIjwbvPx~PUbchI&1Jo(Hc6-!zY4`Q}M9>5z5Zs6bR@Acr%F|g}uGDr^RfUIk!X6}~*E$|8)4CI!>;sLY0ZKl`O z(b&|w-Jh2@YzIAzMPwVmpo^~+HIy95X7TZ1xhmmX7ou}s9fUWu_vw3j?n2pL0FmpX Fe*ifSS-=1Q diff --git a/priv/static/adminfe/chunk-3d1c.b2eb7234.css b/priv/static/adminfe/chunk-3d1c.b2eb7234.css deleted file mode 100644 index ba85e77d555e97cbaf09bdaef884580de5d557c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 723 zcmZ{iL2iRE5Jj)Trn?R@RBa@4l&XsX4`9{UmOV*`qTIchKokWkcB40s?{D{nio&-- zMmWKtXby^$__@NF>R-)JyAjan&dP=?Q>b8w&>DJ~&Io9xA+Dg((Hp$TCsXy9Et1Lp zm?dd7VCb}!W$DLER34SmwgW>g%i`0Iw|0V+;(iK|#5oz57hviq?DZ$HoyvND0=wXjexrTmwm?m-3v{iq`ArI|Qal$V z2ei>+m~Q2kduL2`+(~V8WQcq*1bp!%t+TY&Dn)fa)Q5Px<$Azwr>r|sK8Q>Y-6rI9 zDMutMGV(D}+*0dx2Ho{6%el$eyEFKpPslreXBv5Ve)Cdgv?b_i7JME2xSj=`oPWX$ B0WSam diff --git a/priv/static/adminfe/chunk-4df4.e217dea0.css b/priv/static/adminfe/chunk-4df4.e217dea0.css deleted file mode 100644 index 4672a9f758af89f89f69ce05a0216d8cece66601..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3624 zcmcIm>u%dR41N^@!+;H#3@1(6&i-@W<6szMn{l+tmLVxkvcT^?QWsykv>Ubs0h~mn zXkI>kWNIw82g7((sY7QkwJ^Qp=bRm(d=i$G>QszptzkO}r}-KEFJ{V&OVMQtDp(3+q)q|uFMZn3aU&L6ni_+moHTD zHZe)q+1%A~M3m#35l;?Eqb%gD#0G0LGH{IzB$dwJ?Kpjk@fyK!jSmNC)RDY5a4D5W z$7AnziUJ_pLGDO)gmj?M%{@};j8G7_*Dgis2_D3EME3>C8P{4JJr_qbdt$~{IGawZ zk_ug2M1l!Qd}m=uMRm{yWA2rpsWI#~5#%F(`v&yMaunk`Xq-%cI;|EIHj?z>^i5)0 zu)ZICB{x7wnt1ZX`F#A1%CR=C!lJ%-6f_#nMW6|*`rM_JjK}GiW$)vN%L)&MJf-9s zB}JXQgA!o34JO=dAbRTH$U0>q>F0w5ao}Nb^y7A$Ez&<4aU7R89FURAeEOR`uhlWn z5}daQ`H>~MDfx%&6MfRvM|ASUeyz2nMnVdDt(?;)_-xM-a$zx>RWW62NHz3ITSmZq zg0okiMf4K*LEQ=~r?nw+9GHMaM%kxb1SN;ErNzjzaYSG#RrM58gt@`M39|`5ST&?p z;Bd0M?Sna7OuQej1ab3mNe&T?96?MCm8Ac^#cs*y5&Mr0Ag>06{p0~f4TP1+1}q~; zT+!H0fUN(oaqDEyEPJ^Bye+HEm(X5!Te5reV*HLPE9&%RR}dN_+|9g(H~?ZQVq|c) zt{&>@{!2{6hMIGFtUhD&M}|g*@LjR6x)Tz~d3w*QrfJts+}{3TbX>K&U%hPztL2Pc z^$95OK8xR O;Z_a*SmA;`3_k$memCF% diff --git a/priv/static/adminfe/chunk-7de9.889d1da1.css b/priv/static/adminfe/chunk-7de9.889d1da1.css new file mode 100644 index 0000000000000000000000000000000000000000..29f7b475dfee937fd42276ef126a623b6ab48723 GIT binary patch literal 7044 zcmdT}3vb#w6#grbsw=J190dkzmq^w9AJa5Bi2<)k9NDJ85dZxiA3u^nO1l+yJ5{5_ z$B*;;&SSA;t&p0m7%iFHlMTxsxhA>T5?b+Pljc;hiZ?7HwIYj(ovh+z zvwcnFk~e9RIcBHRKC@3$Q%QKe+;4cPSLtNlZv9UD+a4zp1qEVwA{XWE?P3baLB#Zl zNzDsd4OCEa$qLOylQu%uhUjh6Q3_G5>n1~s9j^vf3o1*e2QazTqDDD8x&SRVdcT0* zMDd?&NOn#W%-1vn`Jryax}T?S>J7NK6QT*04M>4fRaaw@4L{Y6nj-CI>bs-y;i81D5gar*5E6e zZ8hk*5R#&ct{VUrL=f`b6>m|Z+2=6pTCS#qdawB0QNAuEAA`%;gg; z+VnIS+*Oytg#omBh$e&8^MLEcF`yV^+i<-SYwa=mRK`Do(=}7dAcx5XHZblDGysA- ziYaa!t{`P#rdI&{@qSI0&iux35u}vJ{7%Nb#o^2OG%$F=eSz``GQJvu8NT@JOL*Wn zOA%jDY$R<{^I=Pg@O50eOF8J36PK+>SxKyJee{~xGqv6I{nYBI=L8;jdzJ35)lk>e zn6G=WnmN$tQ~V)rgf=<;dd&nr@c*_F9Si3g3A?My+^;K~gwX8RYsYcx3=UhWlOOBXr52lf*QY*b$; zdb=V&WONLv(70jG7HG~Db7(}D655y&bX^IV-Yls3$-*(y?@h6(GiOCr(N?j2E+jNL zzJ+c?!?57+bexWa-qBficGLku-7t2@m!^&b|AuFu1D#MQq1C}jgimF5ECEm8ow%#0 z1h6`g1RN)_Ei8zX6>F(L=2mbsTRE&|zN`r&65HusK6$-0f(O$P+jw1IZpF*J^@62j zyR28AqgqXK;L6AeG@m|4CYg1HG&agrqT*$A!*a$Jb9V)n7^XaI*oK&>joNfMem>?O)d}KSjxhGMt^+(5dGeXUE)lKHYEr@Lkn00xNG*uc11xj=W zTcI@a<#)IZ8IB{YOJGd+6AIcL82{mn!k?Mqq5cs+J+yTqQ*I{-m13;cd2I%!cEsR; z*Y@RBeJR)ocr~#T(X|$bSaWDaG_70zaAGa4`u$)}nODzGh9PWr%^0^!Gf{l`Q2%3f zDr$LDD2Quk83<(bB4}oyFE2D%a8{LHOPKwVCvtZ`nJ1G>c)5tD~LG5 z=mg(|%xc2{W^zz?;gm5lov*jAmn@lop^MpF@y#n@W<0<9n0x^c@|hw8+2Ga%uOLZu z@0?!kW8r17>&WR4vyii~YX@3a?!MPZ72_O=eYr!ZZ=en5ChJD)zIJA<;e zq%i?877s6buJaAhzp>p7ZQF;EKbe1V4MEj8{e!aq4-?3U`w#2tRDbTyF2+p6p)C&o E0gxR)-T(jq literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/chunk-7f8e.b6944d38.css b/priv/static/adminfe/chunk-7f8e.d508c376.css similarity index 100% rename from priv/static/adminfe/chunk-7f8e.b6944d38.css rename to priv/static/adminfe/chunk-7f8e.d508c376.css diff --git a/priv/static/adminfe/chunk-538a.062aa087.css b/priv/static/adminfe/chunk-9bb0.2a82c722.css similarity index 62% rename from priv/static/adminfe/chunk-538a.062aa087.css rename to priv/static/adminfe/chunk-9bb0.2a82c722.css index 9e23d0fdb80fb09a99ad8017c5e8f3b54830c9d3..c0074e6f7803f95e710260f30559db5772ba4a77 100644 GIT binary patch delta 141 zcmeyv|A&7=7Bh!wQj(#unW5R_BIYav=LvHIf|J7%gy0-w@kDUMSvwG%<*Y6U&Szwq TX108U%rCaf$Q-H3@7Z|)b)_k= delta 141 zcmeyv|A&7=7BfeRfsuKNg@MuJBIYav=LvHIf|J7%gy0-w@kDUMSvwG%<*Y6U&Szwq TX108U%rCaf$Q-H3@7Z|)cGfAn diff --git a/priv/static/adminfe/chunk-a601.62c86eea.css b/priv/static/adminfe/chunk-a601.62c86eea.css new file mode 100644 index 0000000000000000000000000000000000000000..a036b0253f933dd2b3d1ef7006721eabc6563671 GIT binary patch literal 2784 zcmcImZEu?}5dJGj)1*zRBcL7a%$NO-X{rod60gQa#^hyD{`>BXNkSm4x))VV8{hd} zpL=|Z)(O|OtQ5A27NRUor`^H9&anzgV}+AO^G;V_mDG^a2U~+&tsPIZW|uFGcC3}Z zfG79O?zkA)leJjEVJj=Q=4t$KP8!L26*Q7~qSCI-mes}rD>~ z3(ki$D%mzl>^a*)@gf~7%q~Bj)h$~p*j=8{_xwVc2_;vWm!KWkoP`_XQ?J6LgSrih zS6L1*ZG;n+QMN+}Qu}Ha%9Jm8U!(+c%0qem{~=%N3;tHZOp6-k&G&YAJQz>YTg59( zwLjUoD8~CLXRO4k@eNofOQA+pt)zw0<4PM_lW?f)SyCF+)jFq+uK9 zv!`bFITE@;p}@9X3X?c}KAho2_!^&1)ZD7 z5gzvKn9RJ#o1SP++CN0gFjiYD8crYmJe|JIXZNoF`d=yF*hl%jLRMuPmVC{!GEE+S z94;7ZOw=cm8Ik&wzRx`Oo>wFAE758c=K*p3Gv$e-4>wvT(})LZj$-^Fj2@jR6x>ww zF!s8HGp`#hi1`tZYIzK?)Q!qc`omh`H;m~nq|iYzG#@x$pEfM#PT=o&s0{btOk+>Z;a nk;O2~QeuCIYhS-K-DG;tBQvjq9&^9ap6TXkl8C?QH$MIb5YPBj literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/chunk-23b2.723b6cc5.css b/priv/static/adminfe/chunk-d01a.03fe0a3f.css similarity index 86% rename from priv/static/adminfe/chunk-23b2.723b6cc5.css rename to priv/static/adminfe/chunk-d01a.03fe0a3f.css index 172bce31757c9858b49e0291c6e5ecb51690ee23..f0b3bf1447fe6b38c52beb47498993a4c9529a6d 100644 GIT binary patch delta 117 zcmdlYxkYlqeNI!e#6)9rBh%Q)H(87)OEXF!iP&ARdrYz6X*CoE%{DY7fZc>b%xyQgJxOYxe&tI!q`& zEbdrCQGwA_|({n6a&U=~#n!f0x>AoVCz6McDU9jsl6IOi&t#u_DYwdc?=>~W% zq`6OpBu2KxBR=V`5r=@4&R{1LiOtoRlY^RCJGrZOe5^s4rd#MN9zGBUKkj%*U#|__ z>aHhfUbC_UaZ0c1^x=|(Hk{TA{z%rHu-&5Zl?7KeS40R+W;wj)Ft9~GqN9UzfUIk! zX0CbCjh1|8U}bTbKO)(#sd8x@jg75a6|&8@eIu=GEj+=mMd*o*{LDhH>4IO8utT)1 zBkIm92cm` zoiLnIoNI`{mz>!y3>u52J!S`?p?$c&k-U%E$tkt-Sa-?lF+qH_?^=A`4rTw3VI#QX zN1z*shW(16gzpsp_R(m>u=8LvTKy{TPH8C*!eK~NQL{aSGMhg-0-bktO%;?l4-~KO z^ZFJ&Z&|p zZ6Wy1;!4Q^graX~NexYIcB@f^)>M<>>%v{Q?f7sgzpIw^hfPAh<8D=*Qcd^Za(RJv zaLvVYHOKo5;zvuC*l&kq?ihm2ni_BHeZ+_-{Sk5BMd!$!i`XJ2S!Id7rB*?xvhV?y z&oDadVXRE<0HD{M0FI3v5%kla9;CX2-MC%PzH~2l KXj|Bw=KTYI%6T#X literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/index.html b/priv/static/adminfe/index.html index 70bb8bd3b..d238accb5 100644 --- a/priv/static/adminfe/index.html +++ b/priv/static/adminfe/index.html @@ -1 +1 @@ -Admin FE
\ No newline at end of file +Admin FE
\ No newline at end of file diff --git a/priv/static/adminfe/static/js/ZhIB.861df339.js b/priv/static/adminfe/static/js/ZhIB.861df339.js new file mode 100644 index 0000000000000000000000000000000000000000..aeec873c88b1e79bf36f41693392ce8a7042fa8a GIT binary patch literal 11328 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 JwcEEp|6jHB(Lw+K literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/static/js/app.19b7049e.js b/priv/static/adminfe/static/js/app.19b7049e.js new file mode 100644 index 0000000000000000000000000000000000000000..d33589df49a42c384c9ebc07f898dffacf47ccdb GIT binary patch literal 180590 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