schedule activity expiration in Oban
authorAlexander Strizhakov <alex.strizhakov@gmail.com>
Sat, 22 Aug 2020 17:46:01 +0000 (20:46 +0300)
committerrinpatch <rinpatch@sdf.org>
Thu, 10 Sep 2020 18:50:40 +0000 (21:50 +0300)
23 files changed:
config/config.exs
config/description.exs
lib/mix/tasks/pleroma/database.ex
lib/pleroma/activity.ex
lib/pleroma/activity_expiration.ex [deleted file]
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/common_api/activity_draft.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/workers/cron/purge_expired_activities_worker.ex [deleted file]
lib/pleroma/workers/purge_expired_activity.ex [new file with mode: 0644]
test/activity_expiration_test.exs [deleted file]
test/activity_test.exs
test/support/factory.ex
test/tasks/database_test.exs
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/mrf/activity_expiration_policy_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/controllers/status_controller_test.exs
test/workers/cron/purge_expired_activities_worker_test.exs [deleted file]
test/workers/purge_expired_activity_test.exs [new file with mode: 0644]

index 95a6ea9db59457d00f51482551b4eefa292c97bc..d975db31e363c6a6b703ced7c140c825306893d5 100644 (file)
@@ -544,7 +544,6 @@ config :pleroma, Oban,
   ],
   plugins: [Oban.Plugins.Pruner],
   crontab: [
-    {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
     {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
     {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
   ]
@@ -655,7 +654,7 @@ config :pleroma, :rate_limit,
   account_confirmation_resend: {8_640_000, 5},
   ap_routes: {60_000, 15}
 
-config :pleroma, Pleroma.ActivityExpiration, enabled: true
+config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true
 
 config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true
 
index 4c4deed30a55d595194db566e6f63ea0e8d5aeea..6ce27278cb06c366c61689632e7cc5f5cfa823b7 100644 (file)
@@ -2290,7 +2290,6 @@ config :pleroma, :config_description, [
         type: {:list, :tuple},
         description: "Settings for cron background jobs",
         suggestions: [
-          {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
           {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
           {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
         ]
index 7d8f00b089b50f6d2dc686a6865a8eec72a38ff1..aab4b5e9a893729f6d1e792125886886765d8758 100644 (file)
@@ -133,8 +133,7 @@ defmodule Mix.Tasks.Pleroma.Database do
     days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
 
     Pleroma.Activity
-    |> join(:left, [a], u in assoc(a, :expiration))
-    |> join(:inner, [a, _u], o in Object,
+    |> join(:inner, [a], o in Object,
       on:
         fragment(
           "(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
@@ -144,14 +143,21 @@ defmodule Mix.Tasks.Pleroma.Database do
         )
     )
     |> where(local: true)
-    |> where([a, u], is_nil(u))
     |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
-    |> where([_a, _u, o], fragment("?->>'type' = 'Note'", o.data))
+    |> where([_a, o], fragment("?->>'type' = 'Note'", o.data))
     |> Pleroma.RepoStreamer.chunk_stream(100)
     |> Stream.each(fn activities ->
       Enum.each(activities, fn activity ->
-        expires_at = Timex.shift(activity.inserted_at, days: days)
-        Pleroma.ActivityExpiration.create(activity, expires_at, false)
+        expires_at =
+          activity.inserted_at
+          |> DateTime.from_naive!("Etc/UTC")
+          |> Timex.shift(days: days)
+
+        Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+          activity_id: activity.id,
+          expires_at: expires_at,
+          validate: false
+        })
       end)
     end)
     |> Stream.run()
index 97feebeaa641b1e17f8ce71e6fd4bdd3a201dfee..03cd3b8c0cd00d6db3ebf384c802b42d8585e5d1 100644 (file)
@@ -7,7 +7,6 @@ defmodule Pleroma.Activity do
 
   alias Pleroma.Activity
   alias Pleroma.Activity.Queries
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Bookmark
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -60,8 +59,6 @@ defmodule Pleroma.Activity do
     # typical case.
     has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
 
-    has_one(:expiration, ActivityExpiration, on_delete: :delete_all)
-
     timestamps()
   end
 
diff --git a/lib/pleroma/activity_expiration.ex b/lib/pleroma/activity_expiration.ex
deleted file mode 100644 (file)
index 955f057..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.ActivityExpiration do
-  use Ecto.Schema
-
-  alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
-  alias Pleroma.Repo
-
-  import Ecto.Changeset
-  import Ecto.Query
-
-  @type t :: %__MODULE__{}
-  @min_activity_lifetime :timer.hours(1)
-
-  schema "activity_expirations" do
-    belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
-    field(:scheduled_at, :naive_datetime)
-  end
-
-  def changeset(%ActivityExpiration{} = expiration, attrs, validate_scheduled_at) do
-    expiration
-    |> cast(attrs, [:scheduled_at])
-    |> validate_required([:scheduled_at])
-    |> validate_scheduled_at(validate_scheduled_at)
-  end
-
-  def get_by_activity_id(activity_id) do
-    ActivityExpiration
-    |> where([exp], exp.activity_id == ^activity_id)
-    |> Repo.one()
-  end
-
-  def create(%Activity{} = activity, scheduled_at, validate_scheduled_at \\ true) do
-    %ActivityExpiration{activity_id: activity.id}
-    |> changeset(%{scheduled_at: scheduled_at}, validate_scheduled_at)
-    |> Repo.insert()
-  end
-
-  def due_expirations(offset \\ 0) do
-    naive_datetime =
-      NaiveDateTime.utc_now()
-      |> NaiveDateTime.add(offset, :millisecond)
-
-    ActivityExpiration
-    |> where([exp], exp.scheduled_at < ^naive_datetime)
-    |> limit(50)
-    |> preload(:activity)
-    |> Repo.all()
-    |> Enum.reject(fn %{activity: activity} ->
-      Activity.pinned_by_actor?(activity)
-    end)
-  end
-
-  def validate_scheduled_at(changeset, false), do: changeset
-
-  def validate_scheduled_at(changeset, true) do
-    validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
-      if not expires_late_enough?(scheduled_at) do
-        [scheduled_at: "an ephemeral activity must live for at least one hour"]
-      else
-        []
-      end
-    end)
-  end
-
-  def expires_late_enough?(scheduled_at) do
-    now = NaiveDateTime.utc_now()
-    diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
-    diff > @min_activity_lifetime
-  end
-end
index 3336214133a9e046ea8ad2a01d793e594602aff1..c33848277d8b8577e1a08e8befb437808b5bc03b 100644 (file)
@@ -5,7 +5,6 @@
 defmodule Pleroma.Web.ActivityPub.ActivityPub do
   alias Pleroma.Activity
   alias Pleroma.Activity.Ir.Topics
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Config
   alias Pleroma.Constants
   alias Pleroma.Conversation
@@ -165,7 +164,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
-    with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+    with {:ok, _job} <-
+           Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+             activity_id: activity.id,
+             expires_at: expires_at
+           }) do
       {:ok, activity}
     end
   end
index 7b4c78e0f86eb7e9afdec34563c78dcc25339611..bee47b4edd4b47e0931d1db953b6b2fd916f61ea 100644 (file)
@@ -31,10 +31,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
 
   defp maybe_add_expiration(activity) do
     days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
-    expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
+    expires_at = DateTime.utc_now() |> Timex.shift(days: days)
 
     with %{"expires_at" => existing_expires_at} <- activity,
-         :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
+         :lt <- DateTime.compare(existing_expires_at, expires_at) do
       activity
     else
       _ -> Map.put(activity, "expires_at", expires_at)
index a5e2323bd61d5f2299cb79b37c9c042b92847f26..b30ca1bd7534db9ba6c63baf37d985c0477b42f3 100644 (file)
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   """
   alias Pleroma.Activity
   alias Pleroma.Activity.Ir.Topics
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Chat
   alias Pleroma.Chat.MessageReference
   alias Pleroma.FollowingRelationship
@@ -189,7 +188,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
       end
 
       if expires_at = activity.data["expires_at"] do
-        ActivityExpiration.create(activity, expires_at)
+        Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+          activity_id: activity.id,
+          expires_at: expires_at
+        })
       end
 
       BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
index f849b2e01fcea1419e8fb7a3b6f1529e84271170..548f76609582c1a1b0695e276e2dd7dd4c02fef9 100644 (file)
@@ -202,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
 
     additional =
       case draft.expires_at do
-        %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
+        %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
         _ -> additional
       end
 
index 4ab533658e41342571c72d1ee64e2d9650c55908..500c3883e9661f18edb444484431f9d38a299d73 100644 (file)
@@ -4,7 +4,6 @@
 
 defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Conversation.Participation
   alias Pleroma.Formatter
   alias Pleroma.Object
@@ -381,9 +380,9 @@ defmodule Pleroma.Web.CommonAPI do
   def check_expiry_date({:ok, nil} = res), do: res
 
   def check_expiry_date({:ok, in_seconds}) do
-    expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
+    expiry = DateTime.add(DateTime.utc_now(), in_seconds)
 
-    if ActivityExpiration.expires_late_enough?(expiry) do
+    if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
       {:ok, expiry}
     else
       {:error, "Expiry date is too soon"}
index 3fe1967be98bf7d62635f0dc1cb091cfd2886a17..ca42917fc4bd06e0749886a7b8058359cad18903 100644 (file)
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   require Pleroma.Constants
 
   alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
   alias Pleroma.HTML
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -245,8 +244,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     expires_at =
       with true <- client_posted_this_activity,
-           %ActivityExpiration{scheduled_at: scheduled_at} <-
-             ActivityExpiration.get_by_activity_id(activity.id) do
+           %Oban.Job{scheduled_at: scheduled_at} <-
+             Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do
         scheduled_at
       else
         _ -> nil
diff --git a/lib/pleroma/workers/cron/purge_expired_activities_worker.ex b/lib/pleroma/workers/cron/purge_expired_activities_worker.ex
deleted file mode 100644 (file)
index 6549207..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
-  @moduledoc """
-  The worker to purge expired activities.
-  """
-
-  use Oban.Worker, queue: "activity_expiration"
-
-  alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
-  alias Pleroma.Config
-  alias Pleroma.User
-  alias Pleroma.Web.CommonAPI
-
-  require Logger
-
-  @interval :timer.minutes(1)
-
-  @impl Oban.Worker
-  def perform(_job) do
-    if Config.get([ActivityExpiration, :enabled]) do
-      Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
-    end
-  after
-    :ok
-  end
-
-  def delete_activity(%ActivityExpiration{activity_id: activity_id}) do
-    with {:activity, %Activity{} = activity} <-
-           {:activity, Activity.get_by_id_with_object(activity_id)},
-         {:user, %User{} = user} <- {:user, User.get_by_ap_id(activity.object.data["actor"])} do
-      CommonAPI.delete(activity.id, user)
-    else
-      {:activity, _} ->
-        Logger.error(
-          "#{__MODULE__} Couldn't delete expired activity: not found activity ##{activity_id}"
-        )
-
-      {:user, _} ->
-        Logger.error(
-          "#{__MODULE__} Couldn't delete expired activity: not found actor of ##{activity_id}"
-        )
-    end
-  end
-end
diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex
new file mode 100644 (file)
index 0000000..016b000
--- /dev/null
@@ -0,0 +1,72 @@
+defmodule Pleroma.Workers.PurgeExpiredActivity do
+  @moduledoc """
+  Worker which purges expired activity.
+  """
+
+  use Oban.Worker, queue: :activity_expiration, max_attempts: 1
+
+  import Ecto.Query
+
+  def enqueue(args) do
+    with true <- enabled?(),
+         args when is_map(args) <- validate_expires_at(args) do
+      {scheduled_at, args} = Map.pop(args, :expires_at)
+
+      args
+      |> __MODULE__.new(scheduled_at: scheduled_at)
+      |> Oban.insert()
+    end
+  end
+
+  @impl true
+  def perform(%Oban.Job{args: %{"activity_id" => id}}) do
+    with %Pleroma.Activity{} = activity <- find_activity(id),
+         %Pleroma.User{} = user <- find_user(activity.object.data["actor"]) do
+      Pleroma.Web.CommonAPI.delete(activity.id, user)
+    end
+  end
+
+  defp enabled? do
+    with false <- Pleroma.Config.get([__MODULE__, :enabled], false) do
+      {:error, :expired_activities_disabled}
+    end
+  end
+
+  defp validate_expires_at(%{validate: false} = args), do: Map.delete(args, :validate)
+
+  defp validate_expires_at(args) do
+    if expires_late_enough?(args[:expires_at]) do
+      args
+    else
+      {:error, :expiration_too_close}
+    end
+  end
+
+  defp find_activity(id) do
+    with nil <- Pleroma.Activity.get_by_id_with_object(id) do
+      {:error, :activity_not_found}
+    end
+  end
+
+  defp find_user(ap_id) do
+    with nil <- Pleroma.User.get_by_ap_id(ap_id) do
+      {:error, :user_not_found}
+    end
+  end
+
+  def get_expiration(id) do
+    from(j in Oban.Job,
+      where: j.state == "scheduled",
+      where: j.queue == "activity_expiration",
+      where: fragment("?->>'activity_id' = ?", j.args, ^id)
+    )
+    |> Pleroma.Repo.one()
+  end
+
+  @spec expires_late_enough?(DateTime.t()) :: boolean()
+  def expires_late_enough?(scheduled_at) do
+    now = DateTime.utc_now()
+    diff = DateTime.diff(scheduled_at, now, :millisecond)
+    diff > :timer.hours(1)
+  end
+end
diff --git a/test/activity_expiration_test.exs b/test/activity_expiration_test.exs
deleted file mode 100644 (file)
index f86d798..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.ActivityExpirationTest do
-  use Pleroma.DataCase
-  alias Pleroma.ActivityExpiration
-  import Pleroma.Factory
-
-  setup do: clear_config([ActivityExpiration, :enabled])
-
-  test "finds activities due to be deleted only" do
-    activity = insert(:note_activity)
-
-    expiration_due =
-      insert(:expiration_in_the_past, %{activity_id: activity.id}) |> Repo.preload(:activity)
-
-    activity2 = insert(:note_activity)
-    insert(:expiration_in_the_future, %{activity_id: activity2.id})
-
-    expirations = ActivityExpiration.due_expirations()
-
-    assert length(expirations) == 1
-    assert hd(expirations) == expiration_due
-  end
-
-  test "denies expirations that don't live long enough" do
-    activity = insert(:note_activity)
-    now = NaiveDateTime.utc_now()
-    assert {:error, _} = ActivityExpiration.create(activity, now)
-  end
-
-  test "deletes an expiration activity" do
-    Pleroma.Config.put([ActivityExpiration, :enabled], true)
-    activity = insert(:note_activity)
-
-    naive_datetime =
-      NaiveDateTime.add(
-        NaiveDateTime.utc_now(),
-        -:timer.minutes(2),
-        :millisecond
-      )
-
-    expiration =
-      insert(
-        :expiration_in_the_past,
-        %{activity_id: activity.id, scheduled_at: naive_datetime}
-      )
-
-    Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
-
-    refute Pleroma.Repo.get(Pleroma.Activity, activity.id)
-    refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id)
-  end
-end
index 2a92327d136dec9768bcf13687acf0ec778baf35..ee6a99cc36b76b638ca6e87f855915d81efcfb09 100644 (file)
@@ -185,15 +185,6 @@ defmodule Pleroma.ActivityTest do
     end
   end
 
-  test "add an activity with an expiration" do
-    activity = insert(:note_activity)
-    insert(:expiration_in_the_future, %{activity_id: activity.id})
-
-    Pleroma.ActivityExpiration
-    |> where([a], a.activity_id == ^activity.id)
-    |> Repo.one!()
-  end
-
   test "all_by_ids_with_object/1" do
     %{id: id1} = insert(:note_activity)
     %{id: id2} = insert(:note_activity)
index 486eda8da5676fc0b86dc39ce16a7b093ec32016..2fdfabbc5a228c0157a418d3ebdfde250c3f28cc 100644 (file)
@@ -200,25 +200,6 @@ defmodule Pleroma.Factory do
     |> Map.merge(attrs)
   end
 
-  defp expiration_offset_by_minutes(attrs, minutes) do
-    scheduled_at =
-      NaiveDateTime.utc_now()
-      |> NaiveDateTime.add(:timer.minutes(minutes), :millisecond)
-      |> NaiveDateTime.truncate(:second)
-
-    %Pleroma.ActivityExpiration{}
-    |> Map.merge(attrs)
-    |> Map.put(:scheduled_at, scheduled_at)
-  end
-
-  def expiration_in_the_past_factory(attrs \\ %{}) do
-    expiration_offset_by_minutes(attrs, -60)
-  end
-
-  def expiration_in_the_future_factory(attrs \\ %{}) do
-    expiration_offset_by_minutes(attrs, 61)
-  end
-
   def article_activity_factory do
     article = insert(:article)
 
index 3a28aa1330c86bd2276c6b6f241231da7a22d94e..292a5ef5f3edaf4b09537308ebd22c530ff38fe8 100644 (file)
@@ -3,14 +3,15 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Mix.Tasks.Pleroma.DatabaseTest do
+  use Pleroma.DataCase
+  use Oban.Testing, repo: Pleroma.Repo
+
   alias Pleroma.Activity
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
 
-  use Pleroma.DataCase
-
   import Pleroma.Factory
 
   setup_all do
@@ -130,40 +131,45 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
 
   describe "ensure_expiration" do
     test "it adds to expiration old statuses" do
-      %{id: activity_id1} = insert(:note_activity)
+      activity1 = insert(:note_activity)
 
-      %{id: activity_id2} =
-        insert(:note_activity, %{inserted_at: NaiveDateTime.from_iso8601!("2015-01-23 23:50:07")})
+      {:ok, inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z")
+      activity2 = insert(:note_activity, %{inserted_at: inserted_at})
 
-      %{id: activity_id3} = activity3 = insert(:note_activity)
+      %{id: activity_id3} = insert(:note_activity)
 
-      expires_at =
-        NaiveDateTime.utc_now()
-        |> NaiveDateTime.add(60 * 61, :second)
-        |> NaiveDateTime.truncate(:second)
+      expires_at = DateTime.add(DateTime.utc_now(), 60 * 61)
 
-      Pleroma.ActivityExpiration.create(activity3, expires_at)
+      Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+        activity_id: activity_id3,
+        expires_at: expires_at
+      })
 
       Mix.Tasks.Pleroma.Database.run(["ensure_expiration"])
 
-      expirations =
-        Pleroma.ActivityExpiration
-        |> order_by(:activity_id)
-        |> Repo.all()
-
-      assert [
-               %Pleroma.ActivityExpiration{
-                 activity_id: ^activity_id1
-               },
-               %Pleroma.ActivityExpiration{
-                 activity_id: ^activity_id2,
-                 scheduled_at: ~N[2016-01-23 23:50:07]
-               },
-               %Pleroma.ActivityExpiration{
-                 activity_id: ^activity_id3,
-                 scheduled_at: ^expires_at
-               }
-             ] = expirations
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: activity1.id},
+        scheduled_at:
+          activity1.inserted_at
+          |> DateTime.from_naive!("Etc/UTC")
+          |> Timex.shift(days: 365)
+      )
+
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: activity2.id},
+        scheduled_at:
+          activity2.inserted_at
+          |> DateTime.from_naive!("Etc/UTC")
+          |> Timex.shift(days: 365)
+      )
+
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: activity_id3},
+        scheduled_at: expires_at
+      )
     end
   end
 end
index 03f968aafa3be3a4f7a3d068decd70e36f0ac763..9af57392483b476cdc160ba2b0ab5e0cd719eab7 100644 (file)
@@ -2069,18 +2069,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
   end
 
   describe "global activity expiration" do
-    setup do: clear_config([:mrf, :policies])
-
     test "creates an activity expiration for local Create activities" do
-      Pleroma.Config.put(
-        [:mrf, :policies],
-        Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy
+      clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
+
+      {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
+      {:ok, follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
+
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: activity.id},
+        scheduled_at:
+          activity.inserted_at
+          |> DateTime.from_naive!("Etc/UTC")
+          |> Timex.shift(days: 365)
       )
 
-      {:ok, %{id: id_create}} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
-      {:ok, _follow} = ActivityBuilder.insert(%{"type" => "Follow", "context" => "3hu"})
-
-      assert [%{activity_id: ^id_create}] = Pleroma.ActivityExpiration |> Repo.all()
+      refute_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: follow.id}
+      )
     end
   end
 
index f25cf8b123d8da415b744f6c1bf4e53d13267501..e7370d4ef6e2d21231ea22f04b45f21883b6c155 100644 (file)
@@ -18,11 +18,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do
                "object" => %{"type" => "Note"}
              })
 
-    assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364
+    assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364
   end
 
   test "keeps existing `expires_at` if it less than the config setting" do
-    expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: 1)
+    expires_at = DateTime.utc_now() |> Timex.shift(days: 1)
 
     assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} =
              ActivityExpirationPolicy.filter(%{
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do
   end
 
   test "overwrites existing `expires_at` if it greater than the config setting" do
-    too_distant_future = NaiveDateTime.utc_now() |> Timex.shift(years: 2)
+    too_distant_future = DateTime.utc_now() |> Timex.shift(years: 2)
 
     assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =
              ActivityExpirationPolicy.filter(%{
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do
                "object" => %{"type" => "Note"}
              })
 
-    assert Timex.diff(expires_at, NaiveDateTime.utc_now(), :days) == 364
+    assert Timex.diff(expires_at, DateTime.utc_now(), :days) == 364
   end
 
   test "ignores remote activities" do
index 800db9a207207921b31b6da87e4433abe6519ee8..5afb0a6dcaa69d0aa6cd5862d08b0f85d72a1558 100644 (file)
@@ -4,6 +4,8 @@
 
 defmodule Pleroma.Web.CommonAPITest do
   use Pleroma.DataCase
+  use Oban.Testing, repo: Pleroma.Repo
+
   alias Pleroma.Activity
   alias Pleroma.Chat
   alias Pleroma.Conversation.Participation
@@ -598,15 +600,15 @@ defmodule Pleroma.Web.CommonAPITest do
     test "it can handle activities that expire" do
       user = insert(:user)
 
-      expires_at =
-        NaiveDateTime.utc_now()
-        |> NaiveDateTime.truncate(:second)
-        |> NaiveDateTime.add(1_000_000, :second)
+      expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
 
       assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
 
-      assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
-      assert expiration.scheduled_at == expires_at
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: activity.id},
+        scheduled_at: expires_at
+      )
     end
   end
 
index f221884e717e8bc6ec74d4167802fa20f8ccc9cb..17a156be8a4bbb1239bc4a242dd3f858f1c4dbae 100644 (file)
@@ -4,9 +4,9 @@
 
 defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   use Pleroma.Web.ConnCase
+  use Oban.Testing, repo: Pleroma.Repo
 
   alias Pleroma.Activity
-  alias Pleroma.ActivityExpiration
   alias Pleroma.Config
   alias Pleroma.Conversation.Participation
   alias Pleroma.Object
@@ -29,8 +29,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     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)
+      Config.put([:instance, :federating], true)
+      Config.get([:instance, :allow_relay], true)
 
       response =
         conn
@@ -103,7 +103,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
       # An activity that will expire:
       # 2 hours
-      expires_in = 120 * 60
+      expires_in = 2 * 60 * 60
+
+      expires_at = DateTime.add(DateTime.utc_now(), expires_in)
 
       conn_four =
         conn
@@ -116,19 +118,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       assert fourth_response =
                %{"id" => fourth_id} = json_response_and_validate_schema(conn_four, 200)
 
-      assert activity = Activity.get_by_id(fourth_id)
-      assert expiration = ActivityExpiration.get_by_activity_id(fourth_id)
-
-      estimated_expires_at =
-        NaiveDateTime.utc_now()
-        |> NaiveDateTime.add(expires_in)
-        |> NaiveDateTime.truncate(:second)
-
-      # This assert will fail if the test takes longer than a minute. I sure hope it never does:
-      assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60
+      assert Activity.get_by_id(fourth_id)
 
-      assert fourth_response["pleroma"]["expires_at"] ==
-               NaiveDateTime.to_iso8601(expiration.scheduled_at)
+      assert_enqueued(
+        worker: Pleroma.Workers.PurgeExpiredActivity,
+        args: %{activity_id: fourth_id},
+        scheduled_at: expires_at
+      )
     end
 
     test "it fails to create a status if `expires_in` is less or equal than an hour", %{
@@ -160,8 +156,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     end
 
     test "Get MRF reason when posting a status is rejected by one", %{conn: conn} do
-      Pleroma.Config.put([:mrf_keyword, :reject], ["GNO"])
-      Pleroma.Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
+      Config.put([:mrf_keyword, :reject], ["GNO"])
+      Config.put([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy])
 
       assert %{"error" => "[KeywordPolicy] Matches with rejected keyword"} =
                conn
@@ -1681,19 +1677,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
 
   test "expires_at is nil for another user" do
     %{conn: conn, user: user} = oauth_access(["read:statuses"])
+    expires_at = DateTime.add(DateTime.utc_now(), 1_000_000)
     {:ok, activity} = CommonAPI.post(user, %{status: "foobar", expires_in: 1_000_000})
 
-    expires_at =
-      activity.id
-      |> ActivityExpiration.get_by_activity_id()
-      |> Map.get(:scheduled_at)
-      |> NaiveDateTime.to_iso8601()
-
-    assert %{"pleroma" => %{"expires_at" => ^expires_at}} =
+    assert %{"pleroma" => %{"expires_at" => a_expires_at}} =
              conn
              |> get("/api/v1/statuses/#{activity.id}")
              |> json_response_and_validate_schema(:ok)
 
+    {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at)
+    assert DateTime.diff(expires_at, a_expires_at) == 0
+
     %{conn: conn} = oauth_access(["read:statuses"])
 
     assert %{"pleroma" => %{"expires_at" => nil}} =
diff --git a/test/workers/cron/purge_expired_activities_worker_test.exs b/test/workers/cron/purge_expired_activities_worker_test.exs
deleted file mode 100644 (file)
index d1acd9a..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorkerTest do
-  use Pleroma.DataCase
-
-  alias Pleroma.ActivityExpiration
-  alias Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker
-
-  import Pleroma.Factory
-  import ExUnit.CaptureLog
-
-  setup do
-    clear_config([ActivityExpiration, :enabled])
-  end
-
-  test "deletes an expiration activity" do
-    Pleroma.Config.put([ActivityExpiration, :enabled], true)
-    activity = insert(:note_activity)
-
-    naive_datetime =
-      NaiveDateTime.add(
-        NaiveDateTime.utc_now(),
-        -:timer.minutes(2),
-        :millisecond
-      )
-
-    expiration =
-      insert(
-        :expiration_in_the_past,
-        %{activity_id: activity.id, scheduled_at: naive_datetime}
-      )
-
-    Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
-
-    refute Pleroma.Repo.get(Pleroma.Activity, activity.id)
-    refute Pleroma.Repo.get(Pleroma.ActivityExpiration, expiration.id)
-  end
-
-  test "works with ActivityExpirationPolicy" do
-    Pleroma.Config.put([ActivityExpiration, :enabled], true)
-
-    clear_config([:mrf, :policies], Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy)
-
-    user = insert(:user)
-
-    days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
-
-    {:ok, %{id: id} = activity} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"})
-
-    past_date =
-      NaiveDateTime.utc_now() |> Timex.shift(days: -days) |> NaiveDateTime.truncate(:second)
-
-    activity
-    |> Repo.preload(:expiration)
-    |> Map.get(:expiration)
-    |> Ecto.Changeset.change(%{scheduled_at: past_date})
-    |> Repo.update!()
-
-    Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker.perform(%Oban.Job{})
-
-    assert [%{data: %{"type" => "Delete", "deleted_activity_id" => ^id}}] =
-             Pleroma.Repo.all(Pleroma.Activity)
-  end
-
-  describe "delete_activity/1" do
-    test "adds log message if activity isn't find" do
-      assert capture_log([level: :error], fn ->
-               PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{
-                 activity_id: "test-activity"
-               })
-             end) =~ "Couldn't delete expired activity: not found activity"
-    end
-
-    test "adds log message if actor isn't find" do
-      assert capture_log([level: :error], fn ->
-               PurgeExpiredActivitiesWorker.delete_activity(%ActivityExpiration{
-                 activity_id: "test-activity"
-               })
-             end) =~ "Couldn't delete expired activity: not found activity"
-    end
-  end
-end
diff --git a/test/workers/purge_expired_activity_test.exs b/test/workers/purge_expired_activity_test.exs
new file mode 100644 (file)
index 0000000..8b5dc9f
--- /dev/null
@@ -0,0 +1,47 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Workers.PurgeExpiredActivityTest do
+  use Pleroma.DataCase, async: true
+  use Oban.Testing, repo: Pleroma.Repo
+
+  import Pleroma.Factory
+
+  alias Pleroma.Workers.PurgeExpiredActivity
+
+  test "denies expirations that don't live long enough" do
+    activity = insert(:note_activity)
+
+    assert {:error, :expiration_too_close} =
+             PurgeExpiredActivity.enqueue(%{
+               activity_id: activity.id,
+               expires_at: DateTime.utc_now()
+             })
+
+    refute_enqueued(
+      worker: Pleroma.Workers.PurgeExpiredActivity,
+      args: %{activity_id: activity.id}
+    )
+  end
+
+  test "enqueue job" do
+    activity = insert(:note_activity)
+
+    assert {:ok, _} =
+             PurgeExpiredActivity.enqueue(%{
+               activity_id: activity.id,
+               expires_at: DateTime.add(DateTime.utc_now(), 3601)
+             })
+
+    assert_enqueued(
+      worker: Pleroma.Workers.PurgeExpiredActivity,
+      args: %{activity_id: activity.id}
+    )
+
+    assert {:ok, _} =
+             perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: activity.id})
+
+    assert %Oban.Job{} = Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id)
+  end
+end