[Enhancement] Add Apprise support (#170)
* [WIP] add settings sidebar entry and placeholder page * [WIP] added placeholder UI and logic for settings form * Added column and UI for apprise server * Add some tests * Added placeholder command runner for apprise * [WIP] Adding apprise package * Added apprise command runner * Hooked up apprise notification module * Ensured apprise was running in verbose mode * Updated wording of apprise notification * Added apprise to README
This commit is contained in:
@@ -1,11 +1,19 @@
|
||||
defmodule Pinchflat.Boot.PreJobStartupTasksTest do
|
||||
use Pinchflat.DataCase
|
||||
|
||||
import Mox
|
||||
import Pinchflat.JobFixtures
|
||||
|
||||
alias Pinchflat.Settings
|
||||
alias Pinchflat.Boot.PreJobStartupTasks
|
||||
|
||||
setup do
|
||||
stub(YtDlpRunnerMock, :version, fn -> {:ok, "1"} end)
|
||||
stub(AppriseRunnerMock, :version, fn -> {:ok, "2"} end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
describe "reset_executing_jobs" do
|
||||
test "resets executing jobs" do
|
||||
job = job_fixture()
|
||||
@@ -13,7 +21,7 @@ defmodule Pinchflat.Boot.PreJobStartupTasksTest do
|
||||
|
||||
assert Repo.reload!(job).state == "executing"
|
||||
|
||||
PreJobStartupTasks.start_link()
|
||||
PreJobStartupTasks.init(%{})
|
||||
|
||||
assert Repo.reload!(job).state == "retryable"
|
||||
end
|
||||
@@ -27,21 +35,31 @@ defmodule Pinchflat.Boot.PreJobStartupTasksTest do
|
||||
|
||||
refute File.exists?(filepath)
|
||||
|
||||
PreJobStartupTasks.start_link()
|
||||
PreJobStartupTasks.init(%{})
|
||||
|
||||
assert File.exists?(filepath)
|
||||
end
|
||||
end
|
||||
|
||||
describe "apply_default_settings" do
|
||||
test "sets default settings" do
|
||||
test "sets yt_dlp version" do
|
||||
Settings.set(yt_dlp_version: nil)
|
||||
|
||||
refute Settings.get!(:yt_dlp_version)
|
||||
|
||||
PreJobStartupTasks.start_link()
|
||||
PreJobStartupTasks.init(%{})
|
||||
|
||||
assert Settings.get!(:yt_dlp_version)
|
||||
end
|
||||
|
||||
test "sets apprise version" do
|
||||
Settings.set(apprise_version: nil)
|
||||
|
||||
refute Settings.get!(:apprise_version)
|
||||
|
||||
PreJobStartupTasks.init(%{})
|
||||
|
||||
assert Settings.get!(:apprise_version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -24,7 +24,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
||||
test "enqueues a new worker for each new media_id in the source's RSS feed", %{source: source} do
|
||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||
|
||||
assert :ok = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||
assert [_] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||
|
||||
assert [worker] = all_enqueued(worker: MediaIndexingWorker)
|
||||
assert worker.args["id"] == source.id
|
||||
@@ -35,10 +35,16 @@ defmodule Pinchflat.FastIndexing.FastIndexingHelpersTest do
|
||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||
media_item_fixture(source_id: source.id, media_id: "test_1")
|
||||
|
||||
assert :ok = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||
assert [] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||
|
||||
refute_enqueued(worker: MediaIndexingWorker)
|
||||
end
|
||||
|
||||
test "returns the IDs of the found media items", %{source: source} do
|
||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||
|
||||
assert ["test_1"] = FastIndexingHelpers.kickoff_indexing_tasks_from_youtube_rss_feed(source)
|
||||
end
|
||||
end
|
||||
|
||||
describe "index_and_enqueue_download_for_media_item/2" do
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorkerTest do
|
||||
import Mox
|
||||
import Pinchflat.SourcesFixtures
|
||||
|
||||
alias Pinchflat.Settings
|
||||
alias Pinchflat.Sources.Source
|
||||
alias Pinchflat.FastIndexing.FastIndexingWorker
|
||||
|
||||
@@ -74,4 +75,28 @@ defmodule Pinchflat.FastIndexing.FastIndexingWorkerTest do
|
||||
assert :ok = perform_job(FastIndexingWorker, %{id: 0})
|
||||
end
|
||||
end
|
||||
|
||||
describe "perform/1 when testing notifications" do
|
||||
setup do
|
||||
Settings.set(apprise_server: "server_1")
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "sends a notification if new media was found" do
|
||||
source = source_fixture(fast_index: true)
|
||||
|
||||
expect(HTTPClientMock, :get, fn _url -> {:ok, "<yt:videoId>test_1</yt:videoId>"} end)
|
||||
|
||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||
assert "server_1" = servers
|
||||
assert is_binary(Keyword.get(opts, :title))
|
||||
assert is_binary(Keyword.get(opts, :body))
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
perform_job(FastIndexingWorker, %{id: source.id})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
defmodule Pinchflat.Notifications.CommandRunnerTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pinchflat.Notifications.CommandRunner, as: Runner
|
||||
|
||||
@original_executable Application.compile_env(:pinchflat, :apprise_executable)
|
||||
|
||||
setup do
|
||||
on_exit(&reset_executable/0)
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "returns :ok when the command succeeds" do
|
||||
assert {:ok, _} = Runner.run("server_1", [])
|
||||
end
|
||||
|
||||
test "includes the servers as the first argument" do
|
||||
assert {:ok, output} = Runner.run(["server_1", "server_2"], [])
|
||||
|
||||
assert String.contains?(output, "server_1 server_2")
|
||||
end
|
||||
|
||||
test "lets you pass a single server as a string" do
|
||||
assert {:ok, output} = Runner.run("server_1", [])
|
||||
|
||||
assert String.contains?(output, "server_1")
|
||||
end
|
||||
|
||||
test "passes all arguments to the command" do
|
||||
assert {:ok, output} = Runner.run("server_1", ["--dry-run"])
|
||||
|
||||
assert String.contains?(output, "--dry-run")
|
||||
end
|
||||
|
||||
test "returns the output when the command fails" do
|
||||
wrap_executable("/bin/false", fn ->
|
||||
assert {:error, ""} = Runner.run("server_1", [])
|
||||
end)
|
||||
end
|
||||
|
||||
test "returns a relevant error if no servers are provided" do
|
||||
assert {:error, :no_servers} = Runner.run(nil, [])
|
||||
assert {:error, :no_servers} = Runner.run("", [])
|
||||
assert {:error, :no_servers} = Runner.run([], [])
|
||||
end
|
||||
end
|
||||
|
||||
describe "version/0" do
|
||||
test "adds the version arg" do
|
||||
assert {:ok, output} = Runner.version()
|
||||
|
||||
assert String.contains?(output, "--version")
|
||||
end
|
||||
end
|
||||
|
||||
defp wrap_executable(new_executable, fun) do
|
||||
Application.put_env(:pinchflat, :apprise_executable, new_executable)
|
||||
fun.()
|
||||
reset_executable()
|
||||
end
|
||||
|
||||
def reset_executable do
|
||||
Application.put_env(:pinchflat, :apprise_executable, @original_executable)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,100 @@
|
||||
defmodule Pinchflat.Notifications.SourceNotificationsTest do
|
||||
use Pinchflat.DataCase
|
||||
|
||||
import Mox
|
||||
import Pinchflat.MediaFixtures
|
||||
import Pinchflat.SourcesFixtures
|
||||
|
||||
alias Pinchflat.Notifications.SourceNotifications
|
||||
|
||||
@apprise_servers ["server_1", "server_2"]
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
describe "wrap_new_media_notification/3" do
|
||||
test "sends a notification when the pending count changes" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||
assert servers == @apprise_servers
|
||||
|
||||
assert opts == [
|
||||
title: "[Pinchflat] New media found",
|
||||
body: "Found 1 new media item(s) for #{source.custom_name}. Downloading them now"
|
||||
]
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
SourceNotifications.wrap_new_media_notification(@apprise_servers, source, fn ->
|
||||
media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
end)
|
||||
end
|
||||
|
||||
test "sends a notification when the downloaded count changes" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||
assert servers == @apprise_servers
|
||||
|
||||
assert opts == [
|
||||
title: "[Pinchflat] New media found",
|
||||
body: "Found 1 new media item(s) for #{source.custom_name}. Downloading them now"
|
||||
]
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
SourceNotifications.wrap_new_media_notification(@apprise_servers, source, fn ->
|
||||
media_item_fixture(%{source_id: source.id, media_filepath: "file.mp4"})
|
||||
end)
|
||||
end
|
||||
|
||||
test "does not send a notification when the count does not change" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(AppriseRunnerMock, :run, 0, fn _, _ -> {:ok, ""} end)
|
||||
|
||||
SourceNotifications.wrap_new_media_notification(@apprise_servers, source, fn ->
|
||||
media_item_fixture(%{source_id: source.id, prevent_download: true, media_filepath: nil})
|
||||
end)
|
||||
end
|
||||
|
||||
test "returns the value of the function" do
|
||||
source = source_fixture()
|
||||
expect(AppriseRunnerMock, :run, 0, fn _, _ -> {:ok, ""} end)
|
||||
|
||||
retval = SourceNotifications.wrap_new_media_notification(@apprise_servers, source, fn -> "value" end)
|
||||
|
||||
assert retval == "value"
|
||||
end
|
||||
end
|
||||
|
||||
describe "send_new_media_notification/3" do
|
||||
test "sends a notification when count is positive" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||
assert servers == @apprise_servers
|
||||
|
||||
assert opts == [
|
||||
title: "[Pinchflat] New media found",
|
||||
body: "Found 1 new media item(s) for #{source.custom_name}. Downloading them now"
|
||||
]
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
:ok = SourceNotifications.send_new_media_notification(@apprise_servers, source, 1)
|
||||
end
|
||||
|
||||
test "does not send a notification when count not positive" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(AppriseRunnerMock, :run, 0, fn _, _ -> {:ok, ""} end)
|
||||
|
||||
:ok = SourceNotifications.send_new_media_notification(@apprise_servers, source, 0)
|
||||
:ok = SourceNotifications.send_new_media_notification(@apprise_servers, source, -1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,6 +24,16 @@ defmodule Pinchflat.SettingsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_setting/2" do
|
||||
test "updates the setting" do
|
||||
setting = Settings.record()
|
||||
|
||||
assert {:ok, false} = Settings.get(:onboarding)
|
||||
assert {:ok, %Setting{}} = Settings.update_setting(setting, %{onboarding: true})
|
||||
assert {:ok, true} = Settings.get(:onboarding)
|
||||
end
|
||||
end
|
||||
|
||||
describe "set/1" do
|
||||
test "updates the setting" do
|
||||
assert {:ok, true} = Settings.set(onboarding: true)
|
||||
@@ -60,4 +70,12 @@ defmodule Pinchflat.SettingsTest do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "change_setting/2" do
|
||||
test "returns a changeset" do
|
||||
setting = Settings.record()
|
||||
|
||||
assert %Ecto.Changeset{} = Settings.change_setting(setting, %{onboarding: true})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule Pinchflat.SlowIndexing.MediaCollectionIndexingWorkerTest do
|
||||
import Pinchflat.SourcesFixtures
|
||||
|
||||
alias Pinchflat.Tasks
|
||||
alias Pinchflat.Settings
|
||||
alias Pinchflat.Sources.Source
|
||||
alias Pinchflat.FastIndexing.FastIndexingWorker
|
||||
alias Pinchflat.Downloading.MediaDownloadWorker
|
||||
@@ -51,6 +52,12 @@ defmodule Pinchflat.SlowIndexing.MediaCollectionIndexingWorkerTest do
|
||||
end
|
||||
|
||||
describe "perform/1" do
|
||||
setup do
|
||||
stub(AppriseRunnerMock, :run, fn _, _ -> {:ok, ""} end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "it indexes the source if it should be indexed" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot, _addl_opts -> {:ok, ""} end)
|
||||
|
||||
@@ -210,4 +217,30 @@ defmodule Pinchflat.SlowIndexing.MediaCollectionIndexingWorkerTest do
|
||||
assert :ok = perform_job(MediaCollectionIndexingWorker, %{id: 0})
|
||||
end
|
||||
end
|
||||
|
||||
describe "perform/1 when testing apprise notifications" do
|
||||
setup do
|
||||
Settings.set(apprise_server: "server_1")
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "sends a notification if new media was found" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot, _addl_opts ->
|
||||
{:ok, source_attributes_return_fixture()}
|
||||
end)
|
||||
|
||||
expect(AppriseRunnerMock, :run, fn servers, opts ->
|
||||
assert "server_1" = servers
|
||||
assert is_binary(Keyword.get(opts, :title))
|
||||
assert is_binary(Keyword.get(opts, :body))
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
perform_job(MediaCollectionIndexingWorker, %{id: source.id})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
defmodule Pinchflat.Utils.CliUtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pinchflat.Utils.CliUtils
|
||||
|
||||
describe "parse_options/1" do
|
||||
test "it converts symbol k-v arg keys to kebab case" do
|
||||
assert ["--buffer-size", "1024"] = CliUtils.parse_options(buffer_size: 1024)
|
||||
end
|
||||
|
||||
test "it keeps string k-v arg keys untouched" do
|
||||
assert ["--under_score", "1024"] = CliUtils.parse_options({"--under_score", 1024})
|
||||
end
|
||||
|
||||
test "it converts symbol arg keys to kebab case" do
|
||||
assert ["--ignore-errors"] = CliUtils.parse_options(:ignore_errors)
|
||||
end
|
||||
|
||||
test "it keeps string arg keys untouched" do
|
||||
assert ["-v"] = CliUtils.parse_options("-v")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -17,31 +17,6 @@ defmodule Pinchflat.YtDlp.CommandRunnerTest do
|
||||
assert {:ok, _output} = Runner.run(@media_url, [], "")
|
||||
end
|
||||
|
||||
test "it converts symbol k-v arg keys to kebab case" do
|
||||
assert {:ok, output} = Runner.run(@media_url, [buffer_size: 1024], "")
|
||||
|
||||
assert String.contains?(output, "--buffer-size 1024")
|
||||
end
|
||||
|
||||
test "it keeps string k-v arg keys untouched" do
|
||||
assert {:ok, output} = Runner.run(@media_url, [{"--under_score", 1024}], "")
|
||||
|
||||
assert String.contains?(output, "--under_score 1024")
|
||||
end
|
||||
|
||||
test "it converts symbol arg keys to kebab case" do
|
||||
assert {:ok, output} = Runner.run(@media_url, [:ignore_errors], "")
|
||||
|
||||
assert String.contains?(output, "--ignore-errors")
|
||||
end
|
||||
|
||||
test "it keeps string arg keys untouched" do
|
||||
assert {:ok, output} = Runner.run(@media_url, ["-v"], "")
|
||||
|
||||
assert String.contains?(output, "-v")
|
||||
refute String.contains?(output, "--v")
|
||||
end
|
||||
|
||||
test "it includes the media url as the first argument" do
|
||||
assert {:ok, output} = Runner.run(@media_url, [:ignore_errors], "")
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
defmodule PinchflatWeb.SettingControllerTest do
|
||||
use PinchflatWeb.ConnCase
|
||||
|
||||
describe "show settings" do
|
||||
test "renders the page", %{conn: conn} do
|
||||
conn = get(conn, ~p"/settings")
|
||||
|
||||
assert html_response(conn, 200) =~ "Settings"
|
||||
end
|
||||
end
|
||||
|
||||
describe "update settings" do
|
||||
test "saves and redirects when data is valid", %{conn: conn} do
|
||||
update_attrs = %{apprise_server: "test://server"}
|
||||
|
||||
conn = put(conn, ~p"/settings", setting: update_attrs)
|
||||
assert redirected_to(conn) == ~p"/settings"
|
||||
|
||||
conn = get(conn, ~p"/settings")
|
||||
assert html_response(conn, 200) =~ update_attrs[:apprise_server]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,9 @@
|
||||
Mox.defmock(YtDlpRunnerMock, for: Pinchflat.YtDlp.BackendCommandRunner)
|
||||
Mox.defmock(YtDlpRunnerMock, for: Pinchflat.YtDlp.YtDlpCommandRunner)
|
||||
Application.put_env(:pinchflat, :yt_dlp_runner, YtDlpRunnerMock)
|
||||
|
||||
Mox.defmock(AppriseRunnerMock, for: Pinchflat.Notifications.AppriseCommandRunner)
|
||||
Application.put_env(:pinchflat, :apprise_runner, AppriseRunnerMock)
|
||||
|
||||
Mox.defmock(HTTPClientMock, for: Pinchflat.HTTP.HTTPBehaviour)
|
||||
Application.put_env(:pinchflat, :http_client, HTTPClientMock)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user