Redo indexing mechanism (#16)
* Bumped up the line length because I fear no man * Refactored indexing Previously, indexing worked by collecting the video IDs of only videos that matched indexing criteria. This new model instead stores ALL videos for a given source, but will only _download_ videos that meet that criteria. This lets us backfill without indexing, makes it easier to add in other backends, lets us download one-off videos for a source that don't quite meet criteria, you name it. * Updated media finders to respect format filters; Added credo file
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
|
||||
use ExUnit.Case, async: true
|
||||
import Mox
|
||||
import Pinchflat.MediaSourceFixtures
|
||||
|
||||
alias Pinchflat.MediaClient.Backends.YtDlp.VideoCollection
|
||||
|
||||
@@ -8,22 +9,23 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
describe "get_video_ids/2" do
|
||||
test "returns a list of video ids with no blank elements" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "id1\nid2\n\nid3\n"} end)
|
||||
describe "get_media_attributes/2" do
|
||||
test "returns a list of video attributes with no blank elements" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture() <> "\n\n"} end)
|
||||
|
||||
assert {:ok, ["id1", "id2", "id3"]} = VideoCollection.get_video_ids(@channel_url)
|
||||
assert {:ok, [%{"id" => "video1"}, %{"id" => "video2"}, %{"id" => "video3"}]} =
|
||||
VideoCollection.get_media_attributes(@channel_url)
|
||||
end
|
||||
|
||||
test "it passes the expected default args" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, opts, ot ->
|
||||
assert opts == [:simulate, :skip_download]
|
||||
assert ot == "%(id)s"
|
||||
assert ot == "%(.{id,title,was_live,original_url})j"
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
assert {:ok, _} = VideoCollection.get_video_ids(@channel_url)
|
||||
assert {:ok, _} = VideoCollection.get_media_attributes(@channel_url)
|
||||
end
|
||||
|
||||
test "it passes the expected custom args" do
|
||||
@@ -33,13 +35,13 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.VideoCollectionTest do
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
assert {:ok, _} = VideoCollection.get_video_ids(@channel_url, [:custom_arg])
|
||||
assert {:ok, _} = VideoCollection.get_media_attributes(@channel_url, [:custom_arg])
|
||||
end
|
||||
|
||||
test "returns the error straight through when the command fails" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end)
|
||||
|
||||
assert {:error, "Big issue", 1} = VideoCollection.get_video_ids(@channel_url)
|
||||
assert {:error, "Big issue", 1} = VideoCollection.get_media_attributes(@channel_url)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do
|
||||
assert {:ok, _} = SourceDetails.get_source_details(@channel_url)
|
||||
end
|
||||
|
||||
test "it returns a struct composed of the returned data" do
|
||||
test "it returns a map composed of the returned data" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||
Phoenix.json_library().encode(%{
|
||||
channel: "TheUselessTrials",
|
||||
@@ -43,42 +43,42 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_video_ids/2 when passed a string" do
|
||||
describe "get_media_attributes/2 when passed a string" do
|
||||
test "it passes the expected arguments to the backend" do
|
||||
expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot ->
|
||||
assert opts == [:simulate, :skip_download]
|
||||
assert ot == "%(id)s"
|
||||
assert ot == "%(.{id,title,was_live,original_url})j"
|
||||
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
assert {:ok, _} = SourceDetails.get_video_ids(@channel_url)
|
||||
assert {:ok, _} = SourceDetails.get_media_attributes(@channel_url)
|
||||
end
|
||||
|
||||
test "it returns a list of strings" do
|
||||
test "it returns a list of maps" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot ->
|
||||
{:ok, "video1\nvideo2\nvideo3"}
|
||||
{:ok, source_attributes_return_fixture()}
|
||||
end)
|
||||
|
||||
assert {:ok, ["video1", "video2", "video3"]} = SourceDetails.get_video_ids(@channel_url)
|
||||
assert {:ok, [%{}, %{}, %{}]} = SourceDetails.get_media_attributes(@channel_url)
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_video_ids/2 when passed a Source record" do
|
||||
describe "get_media_attributes/2 when passed a Source record" do
|
||||
test "it calls the backend with the source's collection ID" do
|
||||
source = source_fixture()
|
||||
|
||||
expect(YtDlpRunnerMock, :run, fn url, _opts, _ot ->
|
||||
assert source.collection_id == url
|
||||
{:ok, "video1\nvideo2\nvideo3"}
|
||||
{:ok, source_attributes_return_fixture()}
|
||||
end)
|
||||
|
||||
assert {:ok, _} = SourceDetails.get_video_ids(source)
|
||||
assert {:ok, _} = SourceDetails.get_media_attributes(source)
|
||||
end
|
||||
|
||||
test "it builds options based on the source's media profile" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, opts, _ot ->
|
||||
assert opts == [{:match_filter, "!was_live"}, :simulate, :skip_download]
|
||||
assert opts == [:simulate, :skip_download]
|
||||
{:ok, ""}
|
||||
end)
|
||||
|
||||
@@ -89,7 +89,7 @@ defmodule Pinchflat.MediaClient.SourceDetailsTest do
|
||||
)
|
||||
|
||||
source = source_fixture(media_profile_id: media_profile.id)
|
||||
assert {:ok, _} = SourceDetails.get_video_ids(source)
|
||||
assert {:ok, _} = SourceDetails.get_media_attributes(source)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do
|
||||
setup do
|
||||
media_item =
|
||||
Repo.preload(
|
||||
media_item_fixture(%{title: nil, media_filepath: nil}),
|
||||
media_item_fixture(%{title: "Something", media_filepath: nil}),
|
||||
[:metadata, source: :media_profile]
|
||||
)
|
||||
|
||||
@@ -58,7 +58,6 @@ defmodule Pinchflat.MediaClient.VideoDownloaderTest do
|
||||
end
|
||||
|
||||
test "it extracts the title", %{media_item: media_item} do
|
||||
assert media_item.title == nil
|
||||
assert {:ok, updated_media_item} = VideoDownloader.download_for_media_item(media_item)
|
||||
assert updated_media_item.title == "Trying to Wheelie Without the Rear Brake"
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ defmodule Pinchflat.MediaSourceTest do
|
||||
import Pinchflat.MediaSourceFixtures
|
||||
|
||||
alias Pinchflat.MediaSource
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.MediaSource.Source
|
||||
alias Pinchflat.Workers.MediaIndexingWorker
|
||||
|
||||
@@ -117,59 +116,6 @@ defmodule Pinchflat.MediaSourceTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "index_media_items/1" do
|
||||
setup do
|
||||
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2\nvideo3"} end)
|
||||
|
||||
{:ok, [source: source_fixture()]}
|
||||
end
|
||||
|
||||
test "it creates a media_item record for each media ID returned", %{source: source} do
|
||||
assert media_items = MediaSource.index_media_items(source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert ["video1", "video2", "video3"] == Enum.map(media_items, & &1.media_id)
|
||||
assert Enum.all?(media_items, fn %MediaItem{} -> true end)
|
||||
end
|
||||
|
||||
test "it attaches all media_items to the given source", %{source: source} do
|
||||
source_id = source.id
|
||||
assert media_items = MediaSource.index_media_items(source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert Enum.all?(media_items, fn %MediaItem{source_id: ^source_id} -> true end)
|
||||
end
|
||||
|
||||
test "it won't duplicate media_items based on media_id and source", %{source: source} do
|
||||
_first_run = MediaSource.index_media_items(source)
|
||||
_duplicate_run = MediaSource.index_media_items(source)
|
||||
|
||||
media_items = Repo.preload(source, :media_items).media_items
|
||||
assert Enum.count(media_items) == 3
|
||||
end
|
||||
|
||||
test "it can duplicate media_ids for different sources", %{source: source} do
|
||||
other_source = source_fixture()
|
||||
|
||||
media_items = MediaSource.index_media_items(source)
|
||||
media_items_other_source = MediaSource.index_media_items(other_source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert Enum.count(media_items_other_source) == 3
|
||||
|
||||
assert Enum.map(media_items, & &1.media_id) ==
|
||||
Enum.map(media_items_other_source, & &1.media_id)
|
||||
end
|
||||
|
||||
test "it returns a list of media_items or changesets", %{source: source} do
|
||||
first_run = MediaSource.index_media_items(source)
|
||||
duplicate_run = MediaSource.index_media_items(source)
|
||||
|
||||
assert Enum.all?(first_run, fn %MediaItem{} -> true end)
|
||||
assert Enum.all?(duplicate_run, fn %Ecto.Changeset{} -> true end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_source/2" do
|
||||
test "updates with valid data updates the source" do
|
||||
source = source_fixture()
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule Pinchflat.MediaTest do
|
||||
|
||||
import Pinchflat.TasksFixtures
|
||||
import Pinchflat.MediaFixtures
|
||||
import Pinchflat.ProfilesFixtures
|
||||
import Pinchflat.MediaSourceFixtures
|
||||
|
||||
alias Pinchflat.Media
|
||||
@@ -30,7 +31,7 @@ defmodule Pinchflat.MediaTest do
|
||||
end
|
||||
|
||||
describe "list_pending_media_items_for/1" do
|
||||
test "it returns pending media_items for a given source" do
|
||||
test "it returns pending without a filepath for a given source" do
|
||||
source = source_fixture()
|
||||
media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
|
||||
@@ -50,6 +51,138 @@ defmodule Pinchflat.MediaTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_pending_media_items_for/1 when testing shorts" do
|
||||
test "returns shorts and normal media when shorts_behaviour is :include" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{shorts_behaviour: :include}).id})
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal, short]
|
||||
end
|
||||
|
||||
test "returns only shorts when shorts_behaviour is :only" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{shorts_behaviour: :only}).id})
|
||||
_normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [short]
|
||||
end
|
||||
|
||||
test "returns only normal media when shorts_behaviour is :exclude" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{shorts_behaviour: :exclude}).id})
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
_short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal]
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_pending_media_items_for/1 when testing livestreams" do
|
||||
test "returns livestreams and normal media when livestream_behaviour is :include" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{livestream_behaviour: :include}).id})
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal, livestream]
|
||||
end
|
||||
|
||||
test "returns only livestreams when livestream_behaviour is :only" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{livestream_behaviour: :only}).id})
|
||||
_normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [livestream]
|
||||
end
|
||||
|
||||
test "returns only normal media when livestream_behaviour is :exclude" do
|
||||
source = source_fixture(%{media_profile_id: media_profile_fixture(%{livestream_behaviour: :exclude}).id})
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
_livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal]
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_pending_media_items_for/1 when testing all format options" do
|
||||
test "returns livestreams, shorts, and normal media when behaviour is :include" do
|
||||
source =
|
||||
source_fixture(%{
|
||||
media_profile_id:
|
||||
media_profile_fixture(%{
|
||||
shorts_behaviour: :include,
|
||||
livestream_behaviour: :include
|
||||
}).id
|
||||
})
|
||||
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal, livestream, short]
|
||||
end
|
||||
|
||||
test "returns only livestreams and shorts when behaviour is :only" do
|
||||
source =
|
||||
source_fixture(%{
|
||||
media_profile_id:
|
||||
media_profile_fixture(%{
|
||||
shorts_behaviour: :only,
|
||||
livestream_behaviour: :only
|
||||
}).id
|
||||
})
|
||||
|
||||
_normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [livestream, short]
|
||||
end
|
||||
|
||||
test "returns only normal media when behaviour is :exclude" do
|
||||
source =
|
||||
source_fixture(%{
|
||||
media_profile_id:
|
||||
media_profile_fixture(%{
|
||||
shorts_behaviour: :exclude,
|
||||
livestream_behaviour: :exclude
|
||||
}).id
|
||||
})
|
||||
|
||||
normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
_livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
_short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [normal]
|
||||
end
|
||||
|
||||
test ":only and :exclude return the expected results" do
|
||||
source =
|
||||
source_fixture(%{
|
||||
media_profile_id:
|
||||
media_profile_fixture(%{
|
||||
shorts_behaviour: :only,
|
||||
livestream_behaviour: :exclude
|
||||
}).id
|
||||
})
|
||||
|
||||
_normal = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
_livestream = media_item_fixture(%{source_id: source.id, media_filepath: nil, livestream: true})
|
||||
short = media_item_fixture(%{source_id: source.id, media_filepath: nil, original_url: "/shorts/"})
|
||||
|
||||
assert Media.list_pending_media_items_for(source) == [short]
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_downloaded_media_items_for/1" do
|
||||
test "returns only media items with a media_filepath" do
|
||||
source = source_fixture()
|
||||
_media_item = media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
media_item = media_item_fixture(%{source_id: source.id, media_filepath: "/video/#{Faker.File.file_name(:video)}"})
|
||||
|
||||
assert Media.list_downloaded_media_items_for(source) == [media_item]
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_media_item!/1" do
|
||||
test "it returns the media_item with given id" do
|
||||
media_item = media_item_fixture()
|
||||
@@ -63,7 +196,8 @@ defmodule Pinchflat.MediaTest do
|
||||
media_id: Faker.String.base64(12),
|
||||
title: Faker.Commerce.product_name(),
|
||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||
source_id: source_fixture().id
|
||||
source_id: source_fixture().id,
|
||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}"
|
||||
}
|
||||
|
||||
assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
defmodule Pinchflat.Profiles.Options.YtDlp.IndexOptionBuilderTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pinchflat.Profiles.MediaProfile
|
||||
alias Pinchflat.Profiles.Options.YtDlp.IndexOptionBuilder
|
||||
|
||||
@media_profile %MediaProfile{
|
||||
output_path_template: "{{ title }}.%(ext)s",
|
||||
shorts_behaviour: :include,
|
||||
livestream_behaviour: :include
|
||||
}
|
||||
|
||||
describe "build/1 when testing release type options" do
|
||||
test "adds correct filter when shorts_behaviour is :only" do
|
||||
media_profile = %MediaProfile{@media_profile | shorts_behaviour: :only}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "original_url*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
refute {:match_filter, "was_live"} in res
|
||||
end
|
||||
|
||||
test "adds correct filter when livestream_behaviour is :only" do
|
||||
media_profile = %MediaProfile{@media_profile | livestream_behaviour: :only}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "was_live"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
refute {:match_filter, "!original_url*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url*=/shorts/"} in res
|
||||
end
|
||||
|
||||
test "adds correct filter when both livestreams and shorts are :only" do
|
||||
media_profile = %MediaProfile{
|
||||
@media_profile
|
||||
| shorts_behaviour: :only,
|
||||
livestream_behaviour: :only
|
||||
}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "original_url*=/shorts/"} in res
|
||||
assert {:match_filter, "was_live"} in res
|
||||
refute {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
end
|
||||
|
||||
test "adds correct filter when shorts_behaviour is :exclude" do
|
||||
media_profile = %MediaProfile{@media_profile | shorts_behaviour: :exclude}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url*=/shorts/"} in res
|
||||
refute {:match_filter, "was_live"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
end
|
||||
|
||||
test "adds correct filter when livestream_behaviour is :exclude" do
|
||||
media_profile = %MediaProfile{@media_profile | livestream_behaviour: :exclude}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "!was_live"} in res
|
||||
refute {:match_filter, "was_live"} in res
|
||||
refute {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url*=/shorts/"} in res
|
||||
end
|
||||
|
||||
test "adds correct filter when shorts and livestreams are both exclude" do
|
||||
media_profile = %MediaProfile{
|
||||
@media_profile
|
||||
| shorts_behaviour: :exclude,
|
||||
livestream_behaviour: :exclude
|
||||
}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "!was_live & original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
refute {:match_filter, "original_url*=/shorts/"} in res
|
||||
refute {:match_filter, "was_live"} in res
|
||||
end
|
||||
|
||||
test "does not add exclusion filter if one is excluded and the other is only" do
|
||||
media_profile = %MediaProfile{
|
||||
@media_profile
|
||||
| shorts_behaviour: :exclude,
|
||||
livestream_behaviour: :only
|
||||
}
|
||||
|
||||
assert {:ok, res} = IndexOptionBuilder.build(media_profile)
|
||||
|
||||
assert {:match_filter, "was_live"} in res
|
||||
refute {:match_filter, "original_url!*=/shorts/"} in res
|
||||
refute {:match_filter, "original_url*=/shorts/"} in res
|
||||
refute {:match_filter, "!was_live"} in res
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,7 @@
|
||||
defmodule Pinchflat.Tasks.SourceTasksTest do
|
||||
use Pinchflat.DataCase
|
||||
|
||||
import Mox
|
||||
import Pinchflat.TasksFixtures
|
||||
import Pinchflat.MediaFixtures
|
||||
import Pinchflat.MediaSourceFixtures
|
||||
@@ -8,9 +9,12 @@ defmodule Pinchflat.Tasks.SourceTasksTest do
|
||||
alias Pinchflat.Tasks
|
||||
alias Pinchflat.Tasks.Task
|
||||
alias Pinchflat.Tasks.SourceTasks
|
||||
alias Pinchflat.Media.MediaItem
|
||||
alias Pinchflat.Workers.MediaIndexingWorker
|
||||
alias Pinchflat.Workers.VideoDownloadWorker
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
describe "kickoff_indexing_task/1" do
|
||||
test "it does not schedule a job if the interval is <= 0" do
|
||||
source = source_fixture(index_frequency_minutes: -1)
|
||||
@@ -46,6 +50,61 @@ defmodule Pinchflat.Tasks.SourceTasksTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "index_media_items/1" do
|
||||
setup do
|
||||
stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
{:ok, [source: source_fixture()]}
|
||||
end
|
||||
|
||||
test "it creates a media_item record for each media ID returned", %{source: source} do
|
||||
assert media_items = SourceTasks.index_media_items(source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert ["video1", "video2", "video3"] == Enum.map(media_items, & &1.media_id)
|
||||
assert ["Video 1", "Video 2", "Video 3"] == Enum.map(media_items, & &1.title)
|
||||
assert Enum.all?(media_items, fn mi -> mi.original_url end)
|
||||
assert Enum.all?(media_items, fn %MediaItem{} -> true end)
|
||||
end
|
||||
|
||||
test "it attaches all media_items to the given source", %{source: source} do
|
||||
source_id = source.id
|
||||
assert media_items = SourceTasks.index_media_items(source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert Enum.all?(media_items, fn %MediaItem{source_id: ^source_id} -> true end)
|
||||
end
|
||||
|
||||
test "it won't duplicate media_items based on media_id and source", %{source: source} do
|
||||
_first_run = SourceTasks.index_media_items(source)
|
||||
_duplicate_run = SourceTasks.index_media_items(source)
|
||||
|
||||
media_items = Repo.preload(source, :media_items).media_items
|
||||
assert Enum.count(media_items) == 3
|
||||
end
|
||||
|
||||
test "it can duplicate media_ids for different sources", %{source: source} do
|
||||
other_source = source_fixture()
|
||||
|
||||
media_items = SourceTasks.index_media_items(source)
|
||||
media_items_other_source = SourceTasks.index_media_items(other_source)
|
||||
|
||||
assert Enum.count(media_items) == 3
|
||||
assert Enum.count(media_items_other_source) == 3
|
||||
|
||||
assert Enum.map(media_items, & &1.media_id) ==
|
||||
Enum.map(media_items_other_source, & &1.media_id)
|
||||
end
|
||||
|
||||
test "it returns a list of media_items or changesets", %{source: source} do
|
||||
first_run = SourceTasks.index_media_items(source)
|
||||
duplicate_run = SourceTasks.index_media_items(source)
|
||||
|
||||
assert Enum.all?(first_run, fn %MediaItem{} -> true end)
|
||||
assert Enum.all?(duplicate_run, fn %Ecto.Changeset{} -> true end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "enqueue_pending_media_downloads/1" do
|
||||
test "it enqueues a job for each pending media item" do
|
||||
source = source_fixture()
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule Pinchflat.Utils.FunctionUtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Pinchflat.Utils.FunctionUtils
|
||||
|
||||
describe "wrap_ok/1" do
|
||||
test "wraps the provided term in an :ok tuple" do
|
||||
assert FunctionUtils.wrap_ok("hello") == {:ok, "hello"}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -38,32 +38,33 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do
|
||||
end
|
||||
|
||||
test "it kicks off a download job for each pending media item" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1"} end)
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: 10)
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
|
||||
assert [_] = all_enqueued(worker: VideoDownloadWorker)
|
||||
assert length(all_enqueued(worker: VideoDownloadWorker)) == 3
|
||||
end
|
||||
|
||||
test "it starts a job for any pending media item even if it's from another run" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1"} end)
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: 10)
|
||||
media_item_fixture(%{source_id: source.id, media_filepath: nil})
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
|
||||
assert [_, _] = all_enqueued(worker: VideoDownloadWorker)
|
||||
assert length(all_enqueued(worker: VideoDownloadWorker)) == 4
|
||||
end
|
||||
|
||||
test "it does not kick off a job for media items that could not be saved" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo1"} end)
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: 10)
|
||||
media_item_fixture(%{source_id: source.id, media_filepath: nil, media_id: "video1"})
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
|
||||
# Only one job should be enqueued, since the second video is a duplicate
|
||||
assert [_] = all_enqueued(worker: VideoDownloadWorker)
|
||||
# Only 3 jobs should be enqueued, since the first video is a duplicate
|
||||
assert length(all_enqueued(worker: VideoDownloadWorker))
|
||||
end
|
||||
|
||||
test "it reschedules the job based on the index frequency" do
|
||||
@@ -91,7 +92,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do
|
||||
end
|
||||
|
||||
test "it creates the basic media_item records" do
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2"} end)
|
||||
expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, source_attributes_return_fixture()} end)
|
||||
|
||||
source = source_fixture(index_frequency_minutes: 10)
|
||||
|
||||
@@ -102,7 +103,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do
|
||||
|> Enum.map(fn media_item -> media_item.media_id end)
|
||||
end
|
||||
|
||||
assert_changed([from: [], to: ["video1", "video2"]], media_item_fetcher, fn ->
|
||||
assert_changed([from: [], to: ["video1", "video2", "video3"]], media_item_fetcher, fn ->
|
||||
perform_job(MediaIndexingWorker, %{id: source.id})
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -15,6 +15,8 @@ defmodule Pinchflat.MediaFixtures do
|
||||
|> Enum.into(%{
|
||||
media_id: Faker.String.base64(12),
|
||||
title: Faker.Commerce.product_name(),
|
||||
original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}",
|
||||
livestream: false,
|
||||
media_filepath: "/video/#{Faker.File.file_name(:video)}",
|
||||
source_id: MediaSourceFixtures.source_fixture().id
|
||||
})
|
||||
|
||||
@@ -29,4 +29,30 @@ defmodule Pinchflat.MediaSourceFixtures do
|
||||
|
||||
source
|
||||
end
|
||||
|
||||
def source_attributes_return_fixture do
|
||||
source_attributes = [
|
||||
%{
|
||||
id: "video1",
|
||||
title: "Video 1",
|
||||
original_url: "https://example.com/video1",
|
||||
was_live: false
|
||||
},
|
||||
%{
|
||||
id: "video2",
|
||||
title: "Video 2",
|
||||
original_url: "https://example.com/video2",
|
||||
was_live: true
|
||||
},
|
||||
%{
|
||||
id: "video3",
|
||||
title: "Video 3",
|
||||
original_url: "https://example.com/video3",
|
||||
was_live: false
|
||||
}
|
||||
]
|
||||
|
||||
source_attributes
|
||||
|> Enum.map_join("\n", &Phoenix.json_library().encode!(&1))
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user