pinchflat/test/pinchflat/sources_test.exs
Kieran 023f449dbe
[Housekeeping] Pass the current action when calling the yt-dlp runner (#514)
* Updated yt-dlp runner to take an action type

* Added actions to all callers of the yt-dlp runner

* [SQUASH] updated test files to use new mocking strategy

* Removed unneeded alias
2024-12-13 12:29:05 -08:00

929 lines
34 KiB
Elixir

defmodule Pinchflat.SourcesTest do
use Pinchflat.DataCase
import Pinchflat.TasksFixtures
import Pinchflat.MediaFixtures
import Pinchflat.ProfilesFixtures
import Pinchflat.SourcesFixtures
alias Pinchflat.Sources
alias Pinchflat.Sources.Source
alias Pinchflat.Utils.FilesystemUtils
alias Pinchflat.Metadata.MetadataFileHelpers
alias Pinchflat.Downloading.DownloadingHelpers
alias Pinchflat.FastIndexing.FastIndexingWorker
alias Pinchflat.Downloading.MediaDownloadWorker
alias Pinchflat.Metadata.SourceMetadataStorageWorker
alias Pinchflat.SlowIndexing.MediaCollectionIndexingWorker
@invalid_source_attrs %{name: nil, collection_id: nil}
describe "schema" do
test "source_metadata is deleted when the source is deleted" do
source =
source_fixture(%{metadata: %{metadata_filepath: "/metadata.json.gz"}})
metadata = source.metadata
assert {:ok, %Source{}} = Sources.delete_source(source)
assert_raise Ecto.NoResultsError, fn ->
Repo.reload!(metadata)
end
end
test "can be JSON encoded without error" do
source = source_fixture()
assert {:ok, _} = Phoenix.json_library().encode(source)
end
end
describe "output_path_template/1" do
test "returns the source's override if present" do
source = source_fixture(%{output_path_template_override: "/override/{{ title }}.{{ ext }}"})
assert Sources.output_path_template(source) == "/override/{{ title }}.{{ ext }}"
end
test "returns the media profile's template if no override is present" do
media_profile = media_profile_fixture(%{output_path_template: "/profile/{{ title }}.{{ ext }}"})
source = source_fixture(%{media_profile_id: media_profile.id})
assert Sources.output_path_template(source) == "/profile/{{ title }}.{{ ext }}"
end
test "Treats empty strings as being blank" do
media_profile = media_profile_fixture(%{output_path_template: "/profile/{{ title }}.{{ ext }}"})
source = source_fixture(%{media_profile_id: media_profile.id, output_path_template_override: " "})
assert Sources.output_path_template(source) == "/profile/{{ title }}.{{ ext }}"
end
end
describe "list_sources/0" do
test "it returns all sources" do
source = source_fixture()
assert Sources.list_sources() == [source]
end
end
describe "list_sources_for/1" do
test "returns all sources for a given media profile" do
media_profile = media_profile_fixture()
source = source_fixture(media_profile_id: media_profile.id)
assert Sources.list_sources_for(media_profile) == [source]
end
end
describe "get_source!/1" do
test "it returns the source with given id" do
source = source_fixture()
assert Sources.get_source!(source.id) == source
end
end
describe "create_source/2" do
test "automatically sets the UUID" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert String.length(source.uuid) == 36
end
test "UUID is not writable by the user" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
uuid: "some_uuid"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert String.length(source.uuid) == 36
end
test "creates a source and adds name + ID from runner response for channels" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.collection_name == "some channel name"
assert String.starts_with?(source.collection_id, "some_channel_id_")
end
test "creates a source and adds name + ID for playlists" do
expect(YtDlpRunnerMock, :run, &playlist_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/playlist?list=abc123"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.collection_name == "some playlist name"
assert String.starts_with?(source.collection_id, "some_playlist_id_")
end
test "adds an error if the runner fails" do
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, _addl -> {:error, "some error", 1} end)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:error, %Ecto.Changeset{} = changeset} = Sources.create_source(valid_attrs)
assert "could not fetch source details from URL" in errors_on(changeset).original_url
end
test "adds an error if the runner succeeds but the result was invalid JSON" do
expect(YtDlpRunnerMock, :run, fn _url, :get_source_details, _opts, _ot, _addl -> {:ok, "Not JSON"} end)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:error, %Ecto.Changeset{} = changeset} = Sources.create_source(valid_attrs)
assert "could not fetch source details from URL" in errors_on(changeset).original_url
end
test "you can specify a custom custom_name" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
custom_name: "some custom name"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.custom_name == "some custom name"
end
test "friendly name is pulled from collection_name if not specified" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.custom_name == "some channel name"
end
test "creation enforces uniqueness of collection_id scoped to the media_profile and title regex" do
expect(YtDlpRunnerMock, :run, 2, fn _url, :get_source_details, _opts, _ot, _addl ->
{:ok,
Phoenix.json_library().encode!(%{
channel: "some channel name",
channel_id: "some_channel_id_12345678",
playlist_id: "some_channel_id_12345678",
playlist_title: "some channel name - videos"
})}
end)
valid_once_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
title_filter_regex: nil
}
assert {:ok, %Source{}} = Sources.create_source(valid_once_attrs)
assert {:error, %Ecto.Changeset{}} = Sources.create_source(valid_once_attrs)
end
test "creation lets you duplicate collection_ids and profiles as long as the regex is different" do
expect(YtDlpRunnerMock, :run, 2, fn _url, :get_source_details, _opts, _ot, _addl ->
{:ok,
Phoenix.json_library().encode!(%{
channel: "some channel name",
channel_id: "some_channel_id_12345678",
playlist_id: "some_channel_id_12345678",
playlist_title: "some channel name - videos"
})}
end)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
name: "some name",
original_url: "https://www.youtube.com/channel/abc123"
}
source_1_attrs = Map.merge(valid_attrs, %{title_filter_regex: "foo"})
source_2_attrs = Map.merge(valid_attrs, %{title_filter_regex: "bar"})
assert {:ok, %Source{}} = Sources.create_source(source_1_attrs)
assert {:ok, %Source{}} = Sources.create_source(source_2_attrs)
end
test "creation lets you duplicate collection_ids as long as the media profile is different" do
expect(YtDlpRunnerMock, :run, 2, fn _url, :get_source_details, _opts, _ot, _addl ->
{:ok,
Phoenix.json_library().encode!(%{
channel: "some channel name",
channel_id: "some_channel_id_12345678",
playlist_id: "some_channel_id_12345678",
playlist_title: "some channel name - videos"
})}
end)
valid_attrs = %{
name: "some name",
original_url: "https://www.youtube.com/channel/abc123",
title_filter_regex: "TEST"
}
source_1_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id})
source_2_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id})
assert {:ok, %Source{}} = Sources.create_source(source_1_attrs)
assert {:ok, %Source{}} = Sources.create_source(source_2_attrs)
end
test "collection_type is inferred from source details" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
expect(YtDlpRunnerMock, :run, &playlist_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{} = source_1} = Sources.create_source(valid_attrs)
assert {:ok, %Source{} = source_2} = Sources.create_source(valid_attrs)
assert source_1.collection_type == :channel
assert source_2.collection_type == :playlist
end
test "creation with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Sources.create_source(@invalid_source_attrs)
end
test "creation with invalid data fails fast and does not call the runner" do
expect(YtDlpRunnerMock, :run, 0, &channel_mock/5)
assert {:error, %Ecto.Changeset{}} = Sources.create_source(@invalid_source_attrs)
end
test "creation will schedule the indexing task" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "creation schedules an index test even if the index frequency is 0" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
index_frequency_minutes: 0
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "fast_index forces the index frequency to be a default value" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
fast_index: true,
index_frequency_minutes: 0
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.index_frequency_minutes == Source.index_frequency_when_fast_indexing()
end
test "disabling fast index will not change the index frequency" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
fast_index: false,
index_frequency_minutes: 0
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert source.index_frequency_minutes == 0
end
test "creating will kickoff a metadata storage worker" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123",
fast_index: false,
index_frequency_minutes: 0
}
assert {:ok, %Source{} = source} = Sources.create_source(valid_attrs)
assert_enqueued(worker: SourceMetadataStorageWorker, args: %{"id" => source.id})
end
end
describe "create_source/2 when testing options" do
test "run_post_commit_tasks: false won't enqueue post-commit tasks" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
valid_attrs = %{
media_profile_id: media_profile_fixture().id,
original_url: "https://www.youtube.com/channel/abc123"
}
assert {:ok, %Source{}} = Sources.create_source(valid_attrs, run_post_commit_tasks: false)
refute_enqueued(worker: MediaCollectionIndexingWorker)
refute_enqueued(worker: SourceMetadataStorageWorker)
end
end
describe "update_source/3" do
test "updates with valid data updates the source" do
source = source_fixture()
update_attrs = %{collection_name: "some updated name"}
assert {:ok, %Source{} = source} = Sources.update_source(source, update_attrs)
assert source.collection_name == "some updated name"
end
test "updates with invalid data fails fast and does not call the runner" do
expect(YtDlpRunnerMock, :run, 0, &channel_mock/5)
source = source_fixture()
assert {:error, %Ecto.Changeset{}} = Sources.update_source(source, @invalid_source_attrs)
end
test "updating the original_url will re-fetch the source details for channels" do
expect(YtDlpRunnerMock, :run, &channel_mock/5)
source = source_fixture()
update_attrs = %{original_url: "https://www.youtube.com/channel/abc123"}
assert {:ok, %Source{} = source} = Sources.update_source(source, update_attrs)
assert source.collection_name == "some channel name"
assert String.starts_with?(source.collection_id, "some_channel_id_")
end
test "updating the original_url will re-fetch the source details for playlists" do
expect(YtDlpRunnerMock, :run, &playlist_mock/5)
source = source_fixture()
update_attrs = %{original_url: "https://www.youtube.com/playlist?list=abc123"}
assert {:ok, %Source{} = source} = Sources.update_source(source, update_attrs)
assert source.collection_name == "some playlist name"
assert String.starts_with?(source.collection_id, "some_playlist_id_")
end
test "not updating the original_url will not re-fetch the source details" do
expect(YtDlpRunnerMock, :run, 0, &channel_mock/5)
source = source_fixture()
update_attrs = %{name: "some updated name"}
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
end
test "updates with invalid data returns error changeset" do
source = source_fixture()
assert {:error, %Ecto.Changeset{}} =
Sources.update_source(source, @invalid_source_attrs)
assert source == Sources.get_source!(source.id)
end
test "updating will kickoff a metadata storage worker if the original_url changes" do
expect(YtDlpRunnerMock, :run, &playlist_mock/5)
source = source_fixture()
update_attrs = %{original_url: "https://www.youtube.com/channel/cba321"}
assert {:ok, %Source{} = source} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: SourceMetadataStorageWorker, args: %{"id" => source.id})
end
test "updating will not kickoff a metadata storage worker other attrs change" do
source = source_fixture()
update_attrs = %{name: "some new name"}
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: SourceMetadataStorageWorker)
end
end
describe "update_source/3 when testing media download tasks" do
test "enabling the download_media attribute will schedule a download task" do
source = source_fixture(download_media: false)
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{download_media: true}
refute_enqueued(worker: MediaDownloadWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
end
test "disabling the download_media attribute will cancel the download task" do
source = source_fixture(download_media: true, enabled: true)
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{download_media: false}
DownloadingHelpers.enqueue_pending_download_tasks(source)
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaDownloadWorker)
end
test "enabling download_media will not schedule a task if the source is disabled" do
source = source_fixture(download_media: false, enabled: false)
_media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{download_media: true}
refute_enqueued(worker: MediaDownloadWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaDownloadWorker)
end
test "disabling a source will cancel any pending download tasks" do
source = source_fixture(download_media: true, enabled: true)
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{enabled: false}
DownloadingHelpers.enqueue_pending_download_tasks(source)
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaDownloadWorker)
end
test "enabling a source will schedule a download task if download_media is true" do
source = source_fixture(download_media: true, enabled: false)
media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{enabled: true}
refute_enqueued(worker: MediaDownloadWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: MediaDownloadWorker, args: %{"id" => media_item.id})
end
test "enabling a source will not schedule a download task if download_media is false" do
source = source_fixture(download_media: false, enabled: false)
_media_item = media_item_fixture(source_id: source.id, media_filepath: nil)
update_attrs = %{enabled: true}
refute_enqueued(worker: MediaDownloadWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaDownloadWorker)
end
end
describe "update_source/3 when testing slow indexing" do
test "updating the index frequency to >0 will re-schedule the indexing task" do
source = source_fixture()
update_attrs = %{index_frequency_minutes: 123}
assert {:ok, %Source{} = source} = Sources.update_source(source, update_attrs)
assert source.index_frequency_minutes == 123
assert_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "updating the index frequency to 0 will not re-schedule the indexing task" do
source = source_fixture()
update_attrs = %{index_frequency_minutes: 0}
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "updating the index frequency to 0 will delete any pending tasks" do
source = source_fixture()
update_attrs = %{index_frequency_minutes: 0}
{:ok, job_1} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
task_1 = task_fixture(source_id: source.id, job_id: job_1.id)
{:ok, job_2} = Oban.insert(MediaCollectionIndexingWorker.new(%{"id" => source.id}))
task_2 = task_fixture(source_id: source.id, job_id: job_2.id)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_1) end
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task_2) end
end
test "not updating the index frequency will not re-schedule the indexing task or delete tasks" do
source = source_fixture()
task = task_fixture(source_id: source.id)
update_attrs = %{name: "some updated name"}
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert Repo.reload!(task)
refute_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "disabling a source will delete any pending tasks" do
source = source_fixture()
update_attrs = %{enabled: false}
{:ok, job} = Oban.insert(MediaCollectionIndexingWorker.new(%{"id" => source.id}))
task = task_fixture(source_id: source.id, job_id: job.id)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end
end
test "updating the index frequency will not create a task if the source is disabled" do
source = source_fixture(enabled: false)
update_attrs = %{index_frequency_minutes: 123}
refute_enqueued(worker: MediaCollectionIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaCollectionIndexingWorker)
end
test "enabling a source will create a task if the index frequency is >0" do
source = source_fixture(enabled: false, index_frequency_minutes: 123)
update_attrs = %{enabled: true}
refute_enqueued(worker: MediaCollectionIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: MediaCollectionIndexingWorker, args: %{"id" => source.id})
end
test "enabling a source will not create a task if the index frequency is 0" do
source = source_fixture(enabled: false, index_frequency_minutes: 0)
update_attrs = %{enabled: true}
refute_enqueued(worker: MediaCollectionIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: MediaCollectionIndexingWorker)
end
end
describe "update_source/3 when testing fast indexing" do
test "enabling fast_index will schedule a fast indexing task" do
source = source_fixture(fast_index: false)
update_attrs = %{fast_index: true}
refute_enqueued(worker: FastIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: FastIndexingWorker, args: %{"id" => source.id})
end
test "disabling fast_index will cancel the fast indexing task" do
source = source_fixture(fast_index: true)
update_attrs = %{fast_index: false}
{:ok, job} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
task_fixture(source_id: source.id, job_id: job.id)
assert_enqueued(worker: FastIndexingWorker, args: %{"id" => source.id})
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: FastIndexingWorker)
end
test "fast_index forces the index frequency to be a default value" do
source = source_fixture(%{fast_index: true})
update_attrs = %{index_frequency_minutes: 0}
assert {:ok, source} = Sources.update_source(source, update_attrs)
assert source.index_frequency_minutes == Source.index_frequency_when_fast_indexing()
end
test "disabling fast index will not change the index frequency" do
source = source_fixture(%{fast_index: false})
update_attrs = %{index_frequency_minutes: 0}
assert {:ok, source} = Sources.update_source(source, update_attrs)
assert source.index_frequency_minutes == 0
end
test "disabling a source will delete any pending tasks" do
source = source_fixture()
update_attrs = %{enabled: false}
{:ok, job} = Oban.insert(FastIndexingWorker.new(%{"id" => source.id}))
task = task_fixture(source_id: source.id, job_id: job.id)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end
end
test "updating fast indexing will not create a task if the source is disabled" do
source = source_fixture(enabled: false, fast_index: false)
update_attrs = %{fast_index: true}
refute_enqueued(worker: FastIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: FastIndexingWorker)
end
test "enabling a source will create a task if fast_index is true" do
source = source_fixture(enabled: false, fast_index: true)
update_attrs = %{enabled: true}
refute_enqueued(worker: FastIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
assert_enqueued(worker: FastIndexingWorker, args: %{"id" => source.id})
end
test "enabling a source will not create a task if fast_index is false" do
source = source_fixture(enabled: false, fast_index: false)
update_attrs = %{enabled: true}
refute_enqueued(worker: FastIndexingWorker)
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs)
refute_enqueued(worker: FastIndexingWorker)
end
end
describe "update_source/3 when testing options" do
test "run_post_commit_tasks: false won't enqueue post-commit tasks" do
source = source_fixture(%{fast_index: false, download_media: false, index_frequency_minutes: -1})
update_attrs = %{fast_index: true, download_media: true, index_frequency_minutes: 100}
assert {:ok, %Source{}} = Sources.update_source(source, update_attrs, run_post_commit_tasks: false)
refute_enqueued(worker: MediaCollectionIndexingWorker)
refute_enqueued(worker: SourceMetadataStorageWorker)
refute_enqueued(worker: MediaDownloadWorker)
refute_enqueued(worker: FastIndexingWorker)
end
end
describe "delete_source/2" do
test "it deletes the source" do
source = source_fixture()
assert {:ok, %Source{}} = Sources.delete_source(source)
assert_raise Ecto.NoResultsError, fn -> Sources.get_source!(source.id) end
end
test "it returns a source changeset" do
source = source_fixture()
assert %Ecto.Changeset{} = Sources.change_source(source)
end
test "deletion also deletes all associated tasks" do
source = source_fixture()
task = task_fixture(source_id: source.id)
assert {:ok, %Source{}} = Sources.delete_source(source)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end
end
test "deletion also deletes all associated media items" do
source = source_fixture()
media_item = media_item_fixture(source_id: source.id)
assert {:ok, %Source{}} = Sources.delete_source(source)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(media_item) end
end
test "deletion does not delete media files by default" do
source = source_fixture()
media_item = media_item_with_attachments(%{source_id: source.id})
assert {:ok, %Source{}} = Sources.delete_source(source)
assert File.exists?(media_item.media_filepath)
end
test "deletes the source's metadata files" do
stub(HTTPClientMock, :get, fn _url, _headers, _opts -> {:ok, ""} end)
source = Repo.preload(source_fixture(), :metadata)
update_attrs = %{
metadata: %{
metadata_filepath: MetadataFileHelpers.compress_and_store_metadata_for(source, %{})
}
}
{:ok, updated_source} = Sources.update_source(source, update_attrs)
assert {:ok, _} = Sources.delete_source(updated_source)
refute File.exists?(updated_source.metadata.metadata_filepath)
end
test "does not delete the source's non-metadata files" do
filepath = FilesystemUtils.generate_metadata_tmpfile(:nfo)
source = source_fixture(%{nfo_filepath: filepath})
assert {:ok, _} = Sources.delete_source(source)
assert File.exists?(filepath)
File.rm!(filepath)
end
end
describe "delete_source/2 when deleting files" do
setup do
stub(UserScriptRunnerMock, :run, fn _event_type, _data -> {:ok, "", 0} end)
:ok
end
test "deletes source and media_items" do
source = source_fixture()
media_item = media_item_with_attachments(%{source_id: source.id})
assert {:ok, %Source{}} = Sources.delete_source(source, delete_files: true)
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(media_item) end
assert_raise Ecto.NoResultsError, fn -> Repo.reload!(source) end
end
test "also deletes media files" do
source = source_fixture()
media_item = media_item_with_attachments(%{source_id: source.id})
assert {:ok, %Source{}} = Sources.delete_source(source, delete_files: true)
refute File.exists?(media_item.media_filepath)
end
test "deletes the source's non-metadata files" do
filepath = FilesystemUtils.generate_metadata_tmpfile(:nfo)
source = source_fixture(%{nfo_filepath: filepath})
assert {:ok, _} = Sources.delete_source(source, delete_files: true)
refute File.exists?(filepath)
end
end
describe "change_source/3" do
test "it returns a changeset" do
source = source_fixture()
assert %Ecto.Changeset{} = Sources.change_source(source)
end
end
describe "change_source/3 when testing regex validation" do
test "succeeds when a valid regex is provided" do
source = source_fixture()
assert %{errors: []} = Sources.change_source(source, %{title_filter_regex: "(?i)^How to Bike$"})
end
test "succeeds when a regex is set back to nil" do
source = source_fixture(%{title_filter_regex: "(?i)^How to Bike$"})
assert %{errors: []} = Sources.change_source(source, %{title_filter_regex: nil})
end
test "fails when an invalid regex is provided" do
source = source_fixture()
changeset = Sources.change_source(source, %{title_filter_regex: "*FOO"})
assert "is invalid" in errors_on(changeset).title_filter_regex
end
end
describe "change_source/3 when testing min/max duration validations" do
test "succeeds if min and max are nil" do
source = source_fixture()
assert %{errors: []} = Sources.change_source(source, %{min_duration_seconds: nil, max_duration_seconds: nil})
end
test "succeeds if either min or max is nil" do
source = source_fixture()
assert %{errors: []} = Sources.change_source(source, %{min_duration_seconds: nil, max_duration_seconds: 100})
assert %{errors: []} = Sources.change_source(source, %{min_duration_seconds: 100, max_duration_seconds: nil})
end
test "succeeds if min is less than max" do
source = source_fixture()
assert %{errors: []} = Sources.change_source(source, %{min_duration_seconds: 100, max_duration_seconds: 200})
end
test "fails if min is greater than or equal to max" do
source = source_fixture()
assert %{errors: [_]} = Sources.change_source(source, %{min_duration_seconds: 200, max_duration_seconds: 100})
assert %{errors: [_]} = Sources.change_source(source, %{min_duration_seconds: 100, max_duration_seconds: 100})
end
end
describe "change_source/3 when testing original_url validation" do
test "succeeds when an original URL is valid" do
source = source_fixture()
valid_urls = [
"https://www.youtube.com/channel/UCkRfArvrzheW2E7b6SVT7vQ",
"https://www.youtube.com/channel/UCkRfArvrzheW2E7b6SVT7vQ/videos",
"https://www.youtube.com/@youtubecreators/featured",
"https://www.youtube.com/@youtubecreators",
"https://www.youtube.com/c/YouTubeCreators",
"https://www.youtube.com/user/YouTubeCreators",
"https://www.youtube.com/YouTubeCreators",
"https://www.youtube.com/playlist?list=PLpjK416fmKwRtq-9-O_NbZlkW0k6zu2Wn",
"https://www.youtube.com/playlist?list=UUkRfArvrzheW2E7b6SVT7vQ"
]
Enum.each(valid_urls, fn url ->
assert %{errors: []} = Sources.change_source(source, %{original_url: url})
end)
end
test "fails when an original URL points to a video" do
source = source_fixture()
invalid_urls = [
"https://www.youtube.com/watch?v=72maj9FLQZI",
"https://youtu.be/72maj9FLQZI",
"https://www.youtube.com/watch?v=1FwGFhMAmBo&list=PLpjK416fmKwRtq-9-O_NbZlkW0k6zu2Wn",
"https://www.youtube.com/shorts/Dq0eH-ZhQTU",
"https://www.youtube.com/embed/X64LHlfx4qg"
]
Enum.each(invalid_urls, fn url ->
assert %{errors: [_]} = Sources.change_source(source, %{original_url: url})
end)
end
test "passes when a non-youtube link is provided" do
source = source_fixture()
valid_urls = [
"https://www.example.com",
"https://www.example.com/playlist",
"https://www.example.com/channel",
"https://www.example.com/user",
"https://www.example.com/watch?v=72maj9FLQZI",
"https://www.example.com/embed/X64LHlfx4qg"
]
Enum.each(valid_urls, fn url ->
assert %{errors: []} = Sources.change_source(source, %{original_url: url})
end)
end
end
defp playlist_mock(_url, :get_source_details, _opts, _ot, _addl) do
{
:ok,
Phoenix.json_library().encode!(%{
channel: nil,
channel_id: nil,
playlist_id: "some_playlist_id_#{:rand.uniform(1_000_000)}",
playlist_title: "some playlist name"
})
}
end
defp channel_mock(_url, :get_source_details, _opts, _ot, _addl) do
channel_id = "some_channel_id_#{:rand.uniform(1_000_000)}"
{
:ok,
Phoenix.json_library().encode!(%{
channel: "some channel name",
channel_id: channel_id,
playlist_id: channel_id,
playlist_title: "some channel name - videos"
})
}
end
end