From: Ivan Tashkinov Date: Sun, 20 Oct 2019 17:43:18 +0000 (+0300) Subject: Merge remote-tracking branch 'remotes/upstream/develop' into 1304-user-info-deprecation X-Git-Url: https://git.squeep.com/?a=commitdiff_plain;h=c6fdfbc4f14b855d9cdf2d757ff2c43282aabe90;p=akkoma Merge remote-tracking branch 'remotes/upstream/develop' into 1304-user-info-deprecation # Conflicts: # CHANGELOG.md # lib/pleroma/notification.ex # lib/pleroma/user.ex # lib/pleroma/user/info.ex # lib/pleroma/web/activity_pub/activity_pub.ex # lib/pleroma/web/admin_api/admin_api_controller.ex # lib/pleroma/web/ostatus/handlers/follow_handler.ex # lib/pleroma/web/ostatus/ostatus.ex # lib/pleroma/web/salmon/salmon.ex # lib/pleroma/web/websub/websub.ex # test/web/admin_api/admin_api_controller_test.exs # test/web/federator_test.exs # test/web/mastodon_api/controllers/conversation_controller_test.exs # test/web/ostatus/ostatus_controller_test.exs # test/web/ostatus/ostatus_test.exs # test/web/salmon/salmon_test.exs # test/web/websub/websub_test.exs --- c6fdfbc4f14b855d9cdf2d757ff2c43282aabe90 diff --cc CHANGELOG.md index 4ce9fa4b0,19cd1e539..cf16cf5df --- a/CHANGELOG.md +++ b/CHANGELOG.md @@@ -29,15 -63,37 +63,38 @@@ The format is based on [Keep a Changelo - MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities - OStatus: Extract RSS functionality - Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`) + - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message + +- Deprecated `User.Info` embedded schema (fields moved to `User`) ### Fixed + - Report emails now include functional links to profiles of remote user accounts +
+ API Changes + - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname` - - Added `:instance, extended_nickname_format` setting to the default config - - Report emails now include functional links to profiles of remote user accounts +
+ + ## [1.1.2] - 2019-10-18 + ### Fixed + - `pleroma_ctl` trying to connect to a running instance when generating the config, which of course doesn't exist. - ## [1.1.0] - 2019-??-?? + ## [1.1.1] - 2019-10-18 + ### Fixed + - One of the migrations between 1.0.0 and 1.1.0 wiping user info of the relay user because of unexpected behavior of postgresql's `jsonb_set`, resulting in inability to post in the default configuration. If you were affected, please run the following query in postgres console, the relay user will be recreated automatically: + ``` + delete from users where ap_id = 'https://your.instance.hostname/relay'; + ``` + - Bad user search matches + + ## [1.1.0] - 2019-10-14 + **Breaking:** The stable branch has been changed from `master` to `stable`. If you want to keep using 1.0, the `release/1.0` branch will receive security updates for 6 months after 1.1 release. + + **OTP Note:** `pleroma_ctl` in 1.0 defaults to `master` and doesn't support specifying arbitrary branches, making `./pleroma_ctl update` fail. To fix this, fetch a version of `pleroma_ctl` from 1.1 using the command below and proceed with the update normally: + ``` + curl -Lo ./bin/pleroma_ctl 'https://git.pleroma.social/pleroma/pleroma/raw/develop/rel/files/bin/pleroma_ctl' + ``` ### Security - Mastodon API: respect post privacy in `/api/v1/statuses/:id/{favourited,reblogged}_by` diff --cc docs/API/admin_api.md index b56f53967,6adeda07e..6faf1786b --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@@ -159,19 -170,76 +170,76 @@@ Note: Available `:permission_group` is - Params: none - Response: - On failure: `{"error": "…"}` - - On success: JSON of the `user.info` + - On success: JSON of the user + ## `POST /api/pleroma/admin/users/permission_group/:permission_group` + + ### Add users to permission group + + - Params: + - `nicknames`: nicknames array + - Response: + - On failure: `{"error": "…"}` + - On success: JSON of the `user.info` + + ## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` + ### Remove user from permission group - - Method: `DELETE` - Params: none - Response: - On failure: `{"error": "…"}` - - On success: JSON of the `user.info` + - On success: JSON of the user - Note: An admin cannot revoke their own admin status. - ## `/api/pleroma/admin/users/:nickname/activation_status` + ## `DELETE /api/pleroma/admin/users/permission_group/:permission_group` + + ### Remove users from permission group + + - Params: + - `nicknames`: nicknames array + - Response: + - On failure: `{"error": "…"}` + - On success: JSON of the `user.info` + - Note: An admin cannot revoke their own admin status. + + ## `PATCH /api/pleroma/admin/users/activate` + + ### Activate user + + - Params: + - `nicknames`: nicknames array + - Response: + + ```json + { + users: [ + { + // user object + } + ] + } + ``` + + ## `PATCH /api/pleroma/admin/users/deactivate` + + ### Deactivate user + + - Params: + - `nicknames`: nicknames array + - Response: + + ```json + { + users: [ + { + // user object + } + ] + } + ``` + + ## DEPRECATED `PATCH /api/pleroma/admin/users/:nickname/activation_status` ### Active or deactivate a user diff --cc lib/pleroma/notification.ex index 6ded91f3c,d145f8d5b..21b26a53d --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@@ -34,40 -35,66 +35,66 @@@ defmodule Pleroma.Notification d end def for_user_query(user, opts \\ []) do - query = - Notification - |> where(user_id: ^user.id) - |> where( - [n, a], + Notification + |> where(user_id: ^user.id) + |> where( + [n, a], + fragment( - "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", ++ "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')", + a.actor + ) + ) + |> join(:inner, [n], activity in assoc(n, :activity)) + |> join(:left, [n, a], object in Object, + on: fragment( - "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')", - a.actor + "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", + object.data, + a.data ) - ) - |> join(:inner, [n], activity in assoc(n, :activity)) - |> join(:left, [n, a], object in Object, - on: - fragment( - "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", - object.data, - a.data - ) - ) - |> preload([n, a, o], activity: {a, object: o}) + ) + |> preload([n, a, o], activity: {a, object: o}) + |> exclude_muted(user, opts) + |> exclude_visibility(opts) + end + + defp exclude_muted(query, _, %{with_muted: true}) do + query + end + + defp exclude_muted(query, user, _opts) do + query - |> where([n, a], a.actor not in ^user.info.muted_notifications) - |> where([n, a], a.actor not in ^user.info.blocks) ++ |> where([n, a], a.actor not in ^user.muted_notifications) ++ |> where([n, a], a.actor not in ^user.blocks) + |> where( + [n, a], - fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks ++ fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks + ) + |> join(:left, [n, a], tm in Pleroma.ThreadMute, + on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) + ) + |> where([n, a, o, tm], is_nil(tm.user_id)) + end + + @valid_visibilities ~w[direct unlisted public private] - if opts[:with_muted] do + defp exclude_visibility(query, %{exclude_visibilities: visibility}) + when is_list(visibility) do + if Enum.all?(visibility, &(&1 in @valid_visibilities)) do query - else - where(query, [n, a], a.actor not in ^user.muted_notifications) - |> where([n, a], a.actor not in ^user.blocks) |> where( [n, a], - fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks - ) - |> join(:left, [n, a], tm in Pleroma.ThreadMute, - on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) + not fragment( + "activity_visibility(?, ?, ?) = ANY (?)", + a.actor, + a.recipients, + a.data, + ^visibility + ) ) - |> where([n, a, o, tm], is_nil(tm.user_id)) + else + Logger.error("Could not exclude visibility to #{visibility}") + query end end diff --cc lib/pleroma/user.ex index 282cf6962,ec705b8f6..e2ebb7d64 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@@ -1209,8 -1048,16 +1198,16 @@@ defmodule Pleroma.User d BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status}) end - def deactivate(%User{} = user, status \\ true) do + def deactivate(user, status \\ true) + + def deactivate(users, status) when is_list(users) do + Repo.transaction(fn -> + for user <- users, do: deactivate(user, status) + end) + end + + def deactivate(%User{} = user, status) do - with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do + with {:ok, user} <- set_activation_status(user, status) do Enum.each(get_followers(user), &invalidate_cache/1) Enum.each(get_friends(user), &update_follower_count/1) @@@ -1218,25 -1065,14 +1215,29 @@@ end end - def update_notification_settings(%User{} = user, settings \\ %{}) do - update_info(user, &User.Info.update_notification_settings(&1, settings)) + def update_notification_settings(%User{} = user, settings) do + settings = + settings + |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end) + |> Map.new() + + notification_settings = + user.notification_settings + |> Map.merge(settings) + |> Map.take(["followers", "follows", "non_follows", "non_followers"]) + + params = %{notification_settings: notification_settings} + + user + |> cast(params, [:notification_settings]) + |> validate_required([:notification_settings]) + |> update_and_set_cache() end + def delete(users) when is_list(users) do + for user <- users, do: delete(user) + end + def delete(%User{} = user) do BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) end diff --cc lib/pleroma/web/activity_pub/activity_pub.ex index b9e88ec70,94c467b69..d4dac5dab --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@@ -235,6 -247,9 +247,7 @@@ defmodule Pleroma.Web.ActivityPub.Activ {:fake, false, activity} <- {:fake, fake, activity}, _ <- increase_replies_count_if_reply(create_data), _ <- increase_poll_votes_if_vote(create_data), + {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity}, - # Changing note count prior to enqueuing federation task in order to avoid - # race conditions on updating user.info {:ok, _actor} <- increase_note_count_if_public(actor, activity), :ok <- maybe_federate(activity) do {:ok, activity} diff --cc lib/pleroma/web/admin_api/admin_api_controller.ex index 3894b3742,b6d3f79c8..66b5276d4 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@@ -375,32 -474,23 +474,40 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI permission: permission_group }) - json(conn, info) + json(conn, fields) end - def right_delete(conn, _) do - render_error(conn, :not_found, "No such permission_group") + def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do + render_error(conn, :forbidden, "You can't revoke your own admin status.") end + def set_activation_status(%{assigns: %{user: admin}} = conn, %{ - "nickname" => nickname, - "status" => status - }) do ++ "nickname" => nickname, ++ "status" => status ++ }) do + with {:ok, status} <- Ecto.Type.cast(:boolean, status), + %User{} = user <- User.get_cached_by_nickname(nickname), + {:ok, _} <- User.deactivate(user, !status) do + action = if(user.deactivated, do: "activate", else: "deactivate") + + ModerationLog.insert_log(%{ + actor: admin, + subject: user, + action: action + }) + + json_response(conn, :no_content, "") + end + end + + def relay_list(conn, _params) do + with {:ok, list} <- Relay.list() do + json(conn, %{relays: list}) + else + _ -> + conn + |> put_status(500) - end - end + def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do with {:ok, _message} <- Relay.follow(target) do ModerationLog.insert_log(%{ diff --cc lib/pleroma/web/masto_fe_controller.ex index cf0fe91ce,93b38e8f4..ca261ad6e --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@@ -34,9 -34,15 +34,15 @@@ defmodule Pleroma.Web.MastoFEControlle end end + @doc "GET /web/manifest.json" + def manifest(conn, _params) do + conn + |> render("manifest.json") + end + @doc "PUT /api/web/settings" def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do - with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do + with {:ok, _} <- User.mastodon_settings_update(user, settings) do json(conn, %{}) else e -> diff --cc test/web/admin_api/admin_api_controller_test.exs index a1027e2e2,9da4940be..58435d23c --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@@ -17,9 -17,15 +17,15 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI alias Pleroma.Web.MediaProxy import Pleroma.Factory - describe "/api/pleroma/admin/users" do - test "Delete" do + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + + :ok + end + + describe "DELETE /api/pleroma/admin/users" do + test "single user" do - admin = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) user = insert(:user) conn = @@@ -39,8 -42,32 +42,32 @@@ assert json_response(conn, 200) == user.nickname end + test "multiple users" do - admin = insert(:user, info: %{is_admin: true}) ++ admin = insert(:user, is_admin: true) + user_one = insert(:user) + user_two = insert(:user) + + conn = + build_conn() + |> assign(:user, admin) + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}" + + response = json_response(conn, 200) + assert response -- [user_one.nickname, user_two.nickname] == [] + end + end + + describe "/api/pleroma/admin/users" do test "Create" do - admin = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) conn = build_conn() @@@ -404,9 -431,10 +431,10 @@@ "@#{admin.nickname} made @#{user.nickname} admin" end - test "/:right DELETE, can remove from a permission group" do + test "/:right POST, can add to a permission group (multiple)" do - admin = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) - user = insert(:user, is_admin: true) + user_one = insert(:user) + user_two = insert(:user) conn = build_conn() @@@ -421,49 -451,45 +451,45 @@@ log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} revoked admin role from @#{user.nickname}" + "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin" end - end - describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do - setup %{conn: conn} do + test "/:right DELETE, can remove from a permission group" do - admin = insert(:user, info: %{is_admin: true}) - user = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) ++ user = insert(:user, is_admin: true) conn = - conn + build_conn() |> assign(:user, admin) |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") - %{conn: conn, admin: admin} - end - - test "deactivates the user", %{conn: conn, admin: admin} do - user = insert(:user) - - conn = - conn - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) - - user = User.get_cached_by_id(user.id) - assert user.deactivated == true - assert json_response(conn, :no_content) + assert json_response(conn, 200) == %{ + "is_admin" => false + } log_entry = Repo.one(ModerationLog) assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deactivated user @#{user.nickname}" + "@#{admin.nickname} revoked admin role from @#{user.nickname}" end - test "activates the user", %{conn: conn, admin: admin} do - user = insert(:user, deactivated: true) + test "/:right DELETE, can remove from a permission group (multiple)" do - admin = insert(:user, info: %{is_admin: true}) - user_one = insert(:user, info: %{is_admin: true}) - user_two = insert(:user, info: %{is_admin: true}) ++ admin = insert(:user, is_admin: true) ++ user_one = insert(:user, is_admin: true) ++ user_two = insert(:user, is_admin: true) conn = - conn - |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true}) + build_conn() + |> assign(:user, admin) + |> put_req_header("accept", "application/json") + |> delete("/api/pleroma/admin/users/permission_group/admin", %{ + nicknames: [user_one.nickname, user_two.nickname] + }) - user = User.get_cached_by_id(user.id) - assert user.deactivated == false - assert json_response(conn, :no_content) + assert json_response(conn, 200) == %{ + "is_admin" => false + } log_entry = Repo.one(ModerationLog) @@@ -1029,8 -1046,52 +1046,52 @@@ end end + test "PATCH /api/pleroma/admin/users/activate" do - admin = insert(:user, info: %{is_admin: true}) - user_one = insert(:user, info: %{deactivated: true}) - user_two = insert(:user, info: %{deactivated: true}) ++ admin = insert(:user, is_admin: true) ++ user_one = insert(:user, deactivated: true) ++ user_two = insert(:user, deactivated: true) + + conn = + build_conn() + |> assign(:user, admin) + |> patch( + "/api/pleroma/admin/users/activate", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["deactivated"]) == [false, false] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}" + end + + test "PATCH /api/pleroma/admin/users/deactivate" do - admin = insert(:user, info: %{is_admin: true}) - user_one = insert(:user, info: %{deactivated: false}) - user_two = insert(:user, info: %{deactivated: false}) ++ admin = insert(:user, is_admin: true) ++ user_one = insert(:user, deactivated: false) ++ user_two = insert(:user, deactivated: false) + + conn = + build_conn() + |> assign(:user, admin) + |> patch( + "/api/pleroma/admin/users/deactivate", + %{nicknames: [user_one.nickname, user_two.nickname]} + ) + + response = json_response(conn, 200) + assert Enum.map(response["users"], & &1["deactivated"]) == [true, true] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" + end + test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do - admin = insert(:user, info: %{is_admin: true}) + admin = insert(:user, is_admin: true) user = insert(:user) conn = @@@ -2483,9 -2544,77 +2544,77 @@@ ObanHelpers.perform_all() - assert User.get_by_id(user.id).info.password_reset_pending == true + assert User.get_by_id(user.id).password_reset_pending == true end end + + describe "relays" do + setup %{conn: conn} do - admin = insert(:user, info: %{is_admin: true}) ++ admin = insert(:user, is_admin: true) + + %{conn: assign(conn, :user, admin), admin: admin} + end + + test "POST /relay", %{admin: admin} do + conn = + build_conn() + |> assign(:user, admin) + |> post("/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + end + + test "GET /relay", %{admin: admin} do + Pleroma.Web.ActivityPub.Relay.get_actor() + |> Ecto.Changeset.change( + following: [ + "http://test-app.com/user/test1", + "http://test-app.com/user/test1", + "http://test-app-42.com/user/test1" + ] + ) + |> Pleroma.User.update_and_set_cache() + + conn = + build_conn() + |> assign(:user, admin) + |> get("/api/pleroma/admin/relay") + + assert json_response(conn, 200)["relays"] -- ["test-app.com", "test-app-42.com"] == [] + end + + test "DELETE /relay", %{admin: admin} do + build_conn() + |> assign(:user, admin) + |> post("/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + conn = + build_conn() + |> assign(:user, admin) + |> delete("/api/pleroma/admin/relay", %{ + relay_url: "http://mastodon.example.org/users/admin" + }) + + assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + + [log_entry_one, log_entry_two] = Repo.all(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry_one) == + "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + + assert ModerationLog.get_log_entry_message(log_entry_two) == + "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin" + end + end end # Needed for testing diff --cc test/web/mastodon_api/controllers/conversation_controller_test.exs index 34b434f3f,d89a87179..b92da665b --- a/test/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@@ -54,9 -54,9 +54,9 @@@ defmodule Pleroma.Web.MastodonAPI.Conve assert user_two.id in account_ids assert user_three.id in account_ids assert is_binary(res_id) - assert unread == true + assert unread == false assert res_last_status["id"] == direct.id - assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 - assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0 ++ assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 end test "updates the last_status on reply", %{conn: conn} do @@@ -107,7 -110,8 +110,8 @@@ |> post("/api/v1/conversations/#{direct_conversation_id}/read") |> json_response(200) - assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0 - assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 + 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 == 0 # The conversation is marked as unread on reply {:ok, _} = @@@ -123,7 -127,8 +127,8 @@@ |> get("/api/v1/conversations") |> json_response(200) - assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1 - assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 ++ assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 # A reply doesn't increment the user's unread_conversation_count if the conversation is unread {:ok, _} = @@@ -133,7 -138,8 +138,8 @@@ "in_reply_to_status_id" => direct.id }) - assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1 - assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0 + assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 ++ assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 end test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do