From d54f9f8a798420251e306a7c5677e843a0c80e72 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Thu, 1 Feb 2024 21:05:13 -0800 Subject: [PATCH] got media source test back working --- lib/pinchflat/media/media_item.ex | 8 +- .../media_client/backends/yt_dlp/channel.ex | 2 +- lib/pinchflat/media_client/channel_details.ex | 4 +- lib/pinchflat/media_source.ex | 36 ++-- lib/pinchflat/media_source/channel.ex | 15 +- lib/pinchflat/tasks.ex | 6 +- lib/pinchflat/tasks/task.ex | 4 +- .../workers/media_indexing_worker.ex | 2 +- .../media_sources/channel_controller.ex | 20 +- ...0724_rename_channel_and_related_fields.exs | 1 + .../backends/yt_dlp/channel_test.exs | 10 +- .../media_client/channel_details_test.exs | 6 +- test/pinchflat/media_source_test.exs | 204 +++++++++--------- test/pinchflat/media_test.exs | 8 +- test/pinchflat/tasks/channel_tasks_test.exs | 8 +- test/pinchflat/tasks_test.exs | 14 +- .../workers/media_indexing_worker_test.exs | 18 +- .../controllers/channel_controller_test.exs | 10 +- test/support/fixtures/media_fixtures.ex | 2 +- .../support/fixtures/media_source_fixtures.ex | 7 +- test/support/fixtures/tasks_fixtures.ex | 2 +- 21 files changed, 197 insertions(+), 190 deletions(-) diff --git a/lib/pinchflat/media/media_item.ex b/lib/pinchflat/media/media_item.ex index 9db15d3..486f24a 100644 --- a/lib/pinchflat/media/media_item.ex +++ b/lib/pinchflat/media/media_item.ex @@ -10,8 +10,8 @@ defmodule Pinchflat.Media.MediaItem do alias Pinchflat.MediaSource.Channel alias Pinchflat.Media.MediaMetadata - @required_fields ~w(media_id channel_id)a - @allowed_fields ~w(title media_id media_filepath channel_id subtitle_filepaths)a + @required_fields ~w(media_id source_id)a + @allowed_fields ~w(title media_id media_filepath source_id subtitle_filepaths)a schema "media_items" do field :title, :string @@ -22,7 +22,7 @@ defmodule Pinchflat.Media.MediaItem do # Will very likely revisit because I can't leave well-enough alone. field :subtitle_filepaths, {:array, {:array, :string}}, default: [] - belongs_to :channel, Channel + belongs_to :channel, Channel, foreign_key: :source_id has_one :metadata, MediaMetadata, on_replace: :update @@ -37,6 +37,6 @@ defmodule Pinchflat.Media.MediaItem do |> cast(attrs, @allowed_fields) |> cast_assoc(:metadata, with: &MediaMetadata.changeset/2, required: false) |> validate_required(@required_fields) - |> unique_constraint([:media_id, :channel_id]) + |> unique_constraint([:media_id, :source_id]) end end diff --git a/lib/pinchflat/media_client/backends/yt_dlp/channel.ex b/lib/pinchflat/media_client/backends/yt_dlp/channel.ex index 8df6f6a..22cb8d6 100644 --- a/lib/pinchflat/media_client/backends/yt_dlp/channel.ex +++ b/lib/pinchflat/media_client/backends/yt_dlp/channel.ex @@ -15,7 +15,7 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.Channel do Returns {:ok, %ChannelDetails{}} | {:error, any, ...}. """ - def get_channel_details(channel_url) do + def get_source_details(channel_url) do opts = [:skip_download, playlist_end: 1] with {:ok, output} <- backend_runner().run(channel_url, opts, "%(.{channel,channel_id})j"), diff --git a/lib/pinchflat/media_client/channel_details.ex b/lib/pinchflat/media_client/channel_details.ex index 021238b..c9909de 100644 --- a/lib/pinchflat/media_client/channel_details.ex +++ b/lib/pinchflat/media_client/channel_details.ex @@ -20,8 +20,8 @@ defmodule Pinchflat.MediaClient.ChannelDetails do Returns {:ok, map()} | {:error, any, ...}. """ - def get_channel_details(channel_url, backend \\ :yt_dlp) do - channel_module(backend).get_channel_details(channel_url) + def get_source_details(channel_url, backend \\ :yt_dlp) do + channel_module(backend).get_source_details(channel_url) end @doc """ diff --git a/lib/pinchflat/media_source.ex b/lib/pinchflat/media_source.ex index 8f8b6f7..0832c56 100644 --- a/lib/pinchflat/media_source.ex +++ b/lib/pinchflat/media_source.ex @@ -15,7 +15,7 @@ defmodule Pinchflat.MediaSource do @doc """ Returns the list of channels. Returns [%Channel{}, ...] """ - def list_channels do + def list_sources do Repo.all(Channel) end @@ -24,7 +24,7 @@ defmodule Pinchflat.MediaSource do Returns %Channel{}. Raises `Ecto.NoResultsError` if the Channel does not exist. """ - def get_channel!(id), do: Repo.get!(Channel, id) + def get_source!(id), do: Repo.get!(Channel, id) @doc """ Creates a channel. May attempt to pull additional channel details from the @@ -33,9 +33,9 @@ defmodule Pinchflat.MediaSource do Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} """ - def create_channel(attrs) do + def create_source(attrs) do %Channel{} - |> change_channel_from_url(attrs) + |> change_source_from_url(attrs) |> commit_and_start_indexing() end @@ -45,12 +45,12 @@ defmodule Pinchflat.MediaSource do Returns [%MediaItem{}, ...] | [%Ecto.Changeset{}, ...] """ - def index_media_items(%Channel{} = channel) do - {:ok, media_ids} = ChannelDetails.get_video_ids(channel.original_url) + def index_media_items(%Channel{} = source) do + {:ok, media_ids} = ChannelDetails.get_video_ids(source.original_url) media_ids |> Enum.map(fn media_id -> - attrs = %{channel_id: channel.id, media_id: media_id} + attrs = %{source_id: source.id, media_id: media_id} case Media.create_media_item(attrs) do {:ok, media_item} -> media_item @@ -69,9 +69,9 @@ defmodule Pinchflat.MediaSource do Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} """ - def update_channel(%Channel{} = channel, attrs) do + def update_source(%Channel{} = channel, attrs) do channel - |> change_channel_from_url(attrs) + |> change_source_from_url(attrs) |> commit_and_start_indexing() end @@ -80,7 +80,7 @@ defmodule Pinchflat.MediaSource do Returns {:ok, %Channel{}} | {:error, %Ecto.Changeset{}} """ - def delete_channel(%Channel{} = channel) do + def delete_source(%Channel{} = channel) do Tasks.delete_tasks_for(channel) Repo.delete(channel) end @@ -88,7 +88,7 @@ defmodule Pinchflat.MediaSource do @doc """ Returns an `%Ecto.Changeset{}` for tracking channel changes. """ - def change_channel(%Channel{} = channel, attrs \\ %{}) do + def change_source(%Channel{} = channel, attrs \\ %{}) do Channel.changeset(channel, attrs) end @@ -101,26 +101,26 @@ defmodule Pinchflat.MediaSource do This means that it'll go for it even if a changeset is otherwise invalid. This is pretty easy to change, but for MVP I'm not concerned. """ - def change_channel_from_url(%Channel{} = channel, attrs) do - case change_channel(channel, attrs) do + def change_source_from_url(%Channel{} = channel, attrs) do + case change_source(channel, attrs) do %Ecto.Changeset{changes: %{original_url: _}} = changeset -> - add_channel_details_to_changeset(channel, changeset) + add_source_details_to_changeset(channel, changeset) changeset -> changeset end end - defp add_channel_details_to_changeset(channel, changeset) do + defp add_source_details_to_changeset(channel, changeset) do %Ecto.Changeset{changes: changes} = changeset - case ChannelDetails.get_channel_details(changes.original_url) do + case ChannelDetails.get_source_details(changes.original_url) do {:ok, %ChannelDetails{} = channel_details} -> - change_channel( + change_source( channel, Map.merge(changes, %{ name: channel_details.name, - channel_id: channel_details.id + collection_id: channel_details.id }) ) diff --git a/lib/pinchflat/media_source/channel.ex b/lib/pinchflat/media_source/channel.ex index e70030c..8dd9cb3 100644 --- a/lib/pinchflat/media_source/channel.ex +++ b/lib/pinchflat/media_source/channel.ex @@ -9,12 +9,13 @@ defmodule Pinchflat.MediaSource.Channel do alias Pinchflat.Media.MediaItem alias Pinchflat.Profiles.MediaProfile - @allowed_fields ~w(name channel_id index_frequency_minutes original_url media_profile_id)a + @allowed_fields ~w(name collection_id collection_type index_frequency_minutes original_url media_profile_id)a @required_fields @allowed_fields -- ~w(index_frequency_minutes)a - schema "channels" do + schema "sources" do field :name, :string - field :channel_id, :string + field :collection_id, :string + field :collection_type, Ecto.Enum, values: [:channel, :playlist] field :index_frequency_minutes, :integer # This should only be used for user reference going forward # as the channel_id should be used for all API calls @@ -22,16 +23,16 @@ defmodule Pinchflat.MediaSource.Channel do belongs_to :media_profile, MediaProfile - has_many :media_items, MediaItem + has_many :media_items, MediaItem, foreign_key: :source_id timestamps(type: :utc_datetime) end @doc false - def changeset(channel, attrs) do - channel + def changeset(source, attrs) do + source |> cast(attrs, @allowed_fields) |> validate_required(@required_fields) - |> unique_constraint([:channel_id, :media_profile_id]) + |> unique_constraint([:collection_id, :media_profile_id]) end end diff --git a/lib/pinchflat/tasks.ex b/lib/pinchflat/tasks.ex index 052b137..f242df9 100644 --- a/lib/pinchflat/tasks.ex +++ b/lib/pinchflat/tasks.ex @@ -71,7 +71,7 @@ defmodule Pinchflat.Tasks do def create_task(%Oban.Job{} = job, attached_record) do attached_record_attr = case attached_record do - %Channel{} = channel -> %{channel_id: channel.id} + %Channel{} = channel -> %{source_id: channel.id} %MediaItem{} = media_item -> %{media_item_id: media_item.id} end @@ -113,7 +113,7 @@ defmodule Pinchflat.Tasks do def delete_tasks_for(attached_record) do tasks = case attached_record do - %Channel{} = channel -> list_tasks_for(:channel_id, channel.id) + %Channel{} = source -> list_tasks_for(:source_id, source.id) %MediaItem{} = media_item -> list_tasks_for(:media_item_id, media_item.id) end @@ -130,7 +130,7 @@ defmodule Pinchflat.Tasks do def delete_pending_tasks_for(attached_record) do tasks = case attached_record do - %Channel{} = channel -> list_pending_tasks_for(:channel_id, channel.id) + %Channel{} = source -> list_pending_tasks_for(:source_id, source.id) %MediaItem{} = media_item -> list_pending_tasks_for(:media_item_id, media_item.id) end diff --git a/lib/pinchflat/tasks/task.ex b/lib/pinchflat/tasks/task.ex index 9d53b60..5106302 100644 --- a/lib/pinchflat/tasks/task.ex +++ b/lib/pinchflat/tasks/task.ex @@ -11,7 +11,7 @@ defmodule Pinchflat.Tasks.Task do schema "tasks" do belongs_to :job, Oban.Job - belongs_to :channel, Channel + belongs_to :channel, Channel, foreign_key: :source_id belongs_to :media_item, MediaItem timestamps(type: :utc_datetime) @@ -20,7 +20,7 @@ defmodule Pinchflat.Tasks.Task do @doc false def changeset(task, attrs) do task - |> cast(attrs, [:job_id, :channel_id, :media_item_id]) + |> cast(attrs, [:job_id, :source_id, :media_item_id]) |> validate_required([:job_id]) end end diff --git a/lib/pinchflat/workers/media_indexing_worker.ex b/lib/pinchflat/workers/media_indexing_worker.ex index 4869b0d..647a74f 100644 --- a/lib/pinchflat/workers/media_indexing_worker.ex +++ b/lib/pinchflat/workers/media_indexing_worker.ex @@ -38,7 +38,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorker do Returns :ok | {:ok, %Task{}} """ def perform(%Oban.Job{args: %{"id" => channel_id}}) do - channel = MediaSource.get_channel!(channel_id) + channel = MediaSource.get_source!(channel_id) if channel.index_frequency_minutes <= 0 do :ok diff --git a/lib/pinchflat_web/controllers/media_sources/channel_controller.ex b/lib/pinchflat_web/controllers/media_sources/channel_controller.ex index 08de4f4..c17cee8 100644 --- a/lib/pinchflat_web/controllers/media_sources/channel_controller.ex +++ b/lib/pinchflat_web/controllers/media_sources/channel_controller.ex @@ -6,19 +6,19 @@ defmodule PinchflatWeb.MediaSources.ChannelController do alias Pinchflat.MediaSource.Channel def index(conn, _params) do - channels = MediaSource.list_channels() + channels = MediaSource.list_sources() render(conn, :index, channels: channels) end def new(conn, _params) do - changeset = MediaSource.change_channel(%Channel{}) + changeset = MediaSource.change_source(%Channel{}) render(conn, :new, changeset: changeset, media_profiles: media_profiles()) end def create(conn, %{"channel" => channel_params}) do - case MediaSource.create_channel(channel_params) do + case MediaSource.create_source(channel_params) do {:ok, channel} -> conn |> put_flash(:info, "Channel created successfully.") @@ -30,22 +30,22 @@ defmodule PinchflatWeb.MediaSources.ChannelController do end def show(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) + channel = MediaSource.get_source!(id) render(conn, :show, channel: channel) end def edit(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) - changeset = MediaSource.change_channel(channel) + channel = MediaSource.get_source!(id) + changeset = MediaSource.change_source(channel) render(conn, :edit, channel: channel, changeset: changeset, media_profiles: media_profiles()) end def update(conn, %{"id" => id, "channel" => channel_params}) do - channel = MediaSource.get_channel!(id) + channel = MediaSource.get_source!(id) - case MediaSource.update_channel(channel, channel_params) do + case MediaSource.update_source(channel, channel_params) do {:ok, channel} -> conn |> put_flash(:info, "Channel updated successfully.") @@ -61,8 +61,8 @@ defmodule PinchflatWeb.MediaSources.ChannelController do end def delete(conn, %{"id" => id}) do - channel = MediaSource.get_channel!(id) - {:ok, _channel} = MediaSource.delete_channel(channel) + channel = MediaSource.get_source!(id) + {:ok, _channel} = MediaSource.delete_source(channel) conn |> put_flash(:info, "Channel deleted successfully.") diff --git a/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs b/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs index 489b7d0..977756f 100644 --- a/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs +++ b/priv/repo/migrations/20240202040724_rename_channel_and_related_fields.exs @@ -9,6 +9,7 @@ defmodule Pinchflat.Repo.Migrations.RenameChannelAndRelatedFields do add :collection_id, :string, null: false add :original_url, :string, null: false add :media_profile_id, references(:media_profiles, on_delete: :restrict), null: false + add :index_frequency_minutes, :integer, default: 60 * 24, null: false timestamps(type: :utc_datetime) end diff --git a/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs b/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs index 8fe2dba..b8fdbb9 100644 --- a/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs +++ b/test/pinchflat/media_client/backends/yt_dlp/channel_test.exs @@ -9,13 +9,13 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do setup :verify_on_exit! - describe "get_channel_details/1" do + describe "get_source_details/1" do test "it returns a %ChannelDetails{} with data on success" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} end) - assert {:ok, res} = Channel.get_channel_details(@channel_url) + assert {:ok, res} = Channel.get_source_details(@channel_url) assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res end @@ -27,19 +27,19 @@ defmodule Pinchflat.MediaClient.Backends.YtDlp.ChannelTest do {:ok, "{}"} end) - assert {:ok, _} = Channel.get_channel_details(@channel_url) + assert {:ok, _} = Channel.get_source_details(@channel_url) end test "it returns an error if the runner returns an error" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:error, "Big issue", 1} end) - assert {:error, "Big issue", 1} = Channel.get_channel_details(@channel_url) + assert {:error, "Big issue", 1} = Channel.get_source_details(@channel_url) end test "it returns an error if the output is not JSON" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "Not JSON"} end) - assert {:error, %Jason.DecodeError{}} = Channel.get_channel_details(@channel_url) + assert {:error, %Jason.DecodeError{}} = Channel.get_source_details(@channel_url) end end end diff --git a/test/pinchflat/media_client/channel_details_test.exs b/test/pinchflat/media_client/channel_details_test.exs index 2a98fa5..7ca3db5 100644 --- a/test/pinchflat/media_client/channel_details_test.exs +++ b/test/pinchflat/media_client/channel_details_test.exs @@ -15,7 +15,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do end end - describe "get_channel_details/2" do + describe "get_source_details/2" do test "it passes the expected arguments to the backend" do expect(YtDlpRunnerMock, :run, fn @channel_url, opts, ot -> assert opts == [:skip_download, playlist_end: 1] @@ -24,7 +24,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} end) - assert {:ok, _} = ChannelDetails.get_channel_details(@channel_url) + assert {:ok, _} = ChannelDetails.get_source_details(@channel_url) end test "it returns a struct composed of the returned data" do @@ -32,7 +32,7 @@ defmodule Pinchflat.MediaClient.ChannelDetailsTest do {:ok, "{\"channel\": \"TheUselessTrials\", \"channel_id\": \"UCQH2\"}"} end) - assert {:ok, res} = ChannelDetails.get_channel_details(@channel_url) + assert {:ok, res} = ChannelDetails.get_source_details(@channel_url) assert %ChannelDetails{id: "UCQH2", name: "TheUselessTrials"} = res end end diff --git a/test/pinchflat/media_source_test.exs b/test/pinchflat/media_source_test.exs index f46cfb3..7ca7187 100644 --- a/test/pinchflat/media_source_test.exs +++ b/test/pinchflat/media_source_test.exs @@ -10,79 +10,82 @@ defmodule Pinchflat.MediaSourceTest do alias Pinchflat.MediaSource.Channel alias Pinchflat.Workers.MediaIndexingWorker - @invalid_channel_attrs %{name: nil, channel_id: nil} + @invalid_source_attrs %{name: nil, collection_id: nil} setup :verify_on_exit! - describe "list_channels/0" do - test "it returns all channels" do - channel = channel_fixture() - assert MediaSource.list_channels() == [channel] + describe "list_sources/0" do + test "it returns all sources" do + source = source_fixture() + assert MediaSource.list_sources() == [source] end end - describe "get_channel!/1" do - test "it returns the channel with given id" do - channel = channel_fixture() - assert MediaSource.get_channel!(channel.id) == channel + describe "get_source!/1" do + test "it returns the source with given id" do + source = source_fixture() + assert MediaSource.get_source!(source.id) == source end end - describe "create_channel/1" do - test "creates a channel and adds name + ID from runner response" do + describe "create_source/1" do + test "creates a source and adds name + ID from runner response" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) valid_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{} = channel} = MediaSource.create_channel(valid_attrs) - assert channel.name == "some name" - assert String.starts_with?(channel.channel_id, "some_channel_id_") + assert {:ok, %Channel{} = source} = MediaSource.create_source(valid_attrs) + assert source.name == "some name" + assert String.starts_with?(source.collection_id, "some_source_id_") end test "creation with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = MediaSource.create_channel(@invalid_channel_attrs) + assert {:error, %Ecto.Changeset{}} = MediaSource.create_source(@invalid_source_attrs) end - test "creation enforces uniqueness of channel_id scoped to the media_profile" do + test "creation enforces uniqueness of source_id scoped to the media_profile" do expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot -> {:ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_12345678" + channel_id: "some_source_id_12345678" })} end) valid_once_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{}} = MediaSource.create_channel(valid_once_attrs) - assert {:error, %Ecto.Changeset{}} = MediaSource.create_channel(valid_once_attrs) + assert {:ok, %Channel{}} = MediaSource.create_source(valid_once_attrs) + assert {:error, %Ecto.Changeset{}} = MediaSource.create_source(valid_once_attrs) end - test "creation lets you duplicate channel_ids as long as the media profile is different" do + test "creation lets you duplicate collection_ids as long as the media profile is different" do expect(YtDlpRunnerMock, :run, 2, fn _url, _opts, _ot -> {:ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_12345678" + channel_id: "some_source_id_12345678" })} end) valid_attrs = %{ name: "some name", - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - channel_1_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) - channel_2_attrs = Map.merge(valid_attrs, %{media_profile_id: media_profile_fixture().id}) + 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, %Channel{}} = MediaSource.create_channel(channel_1_attrs) - assert {:ok, %Channel{}} = MediaSource.create_channel(channel_2_attrs) + assert {:ok, %Channel{}} = MediaSource.create_source(source_1_attrs) + assert {:ok, %Channel{}} = MediaSource.create_source(source_2_attrs) end test "creation will schedule the indexing task" do @@ -90,12 +93,13 @@ defmodule Pinchflat.MediaSourceTest do valid_attrs = %{ media_profile_id: media_profile_fixture().id, - original_url: "https://www.youtube.com/channel/abc123" + original_url: "https://www.youtube.com/channel/abc123", + collection_type: "channel" } - assert {:ok, %Channel{} = channel} = MediaSource.create_channel(valid_attrs) + assert {:ok, %Channel{} = source} = MediaSource.create_source(valid_attrs) - assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end end @@ -103,181 +107,181 @@ defmodule Pinchflat.MediaSourceTest do setup do stub(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2\nvideo3"} end) - {:ok, [channel: channel_fixture()]} + {:ok, [source: source_fixture()]} end - test "it creates a media_item record for each media ID returned", %{channel: channel} do - assert media_items = MediaSource.index_media_items(channel) + 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 channel", %{channel: channel} do - channel_id = channel.id - assert media_items = MediaSource.index_media_items(channel) + 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{channel_id: ^channel_id} -> true end) + 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 channel", %{channel: channel} do - _first_run = MediaSource.index_media_items(channel) - _duplicate_run = MediaSource.index_media_items(channel) + 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(channel, :media_items).media_items + media_items = Repo.preload(source, :media_items).media_items assert Enum.count(media_items) == 3 end - test "it can duplicate media_ids for different channels", %{channel: channel} do - other_channel = channel_fixture() + test "it can duplicate media_ids for different sources", %{source: source} do + other_source = source_fixture() - media_items = MediaSource.index_media_items(channel) - media_items_other_channel = MediaSource.index_media_items(other_channel) + 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_channel) == 3 + assert Enum.count(media_items_other_source) == 3 assert Enum.map(media_items, & &1.media_id) == - Enum.map(media_items_other_channel, & &1.media_id) + Enum.map(media_items_other_source, & &1.media_id) end - test "it returns a list of media_items or changesets", %{channel: channel} do - first_run = MediaSource.index_media_items(channel) - duplicate_run = MediaSource.index_media_items(channel) + 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_channel/2" do - test "updates with valid data updates the channel" do - channel = channel_fixture() + describe "update_source/2" do + test "updates with valid data updates the source" do + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.name == "some updated name" + assert {:ok, %Channel{} = source} = MediaSource.update_source(source, update_attrs) + assert source.name == "some updated name" end - test "updating the original_url will re-fetch the channel details" do + test "updating the original_url will re-fetch the source details" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() update_attrs = %{original_url: "https://www.youtube.com/channel/abc123"} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.name == "some name" - assert String.starts_with?(channel.channel_id, "some_channel_id_") + assert {:ok, %Channel{} = source} = MediaSource.update_source(source, update_attrs) + assert source.name == "some name" + assert String.starts_with?(source.collection_id, "some_source_id_") end - test "not updating the original_url will not re-fetch the channel details" do + test "not updating the original_url will not re-fetch the source details" do expect(YtDlpRunnerMock, :run, 0, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{}} = MediaSource.update_channel(channel, update_attrs) + assert {:ok, %Channel{}} = MediaSource.update_source(source, update_attrs) end test "updating the index frequency will re-schedule the indexing task" do - channel = channel_fixture() + source = source_fixture() update_attrs = %{index_frequency_minutes: 123} - assert {:ok, %Channel{} = channel} = MediaSource.update_channel(channel, update_attrs) - assert channel.index_frequency_minutes == 123 - assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert {:ok, %Channel{} = source} = MediaSource.update_source(source, update_attrs) + assert source.index_frequency_minutes == 123 + assert_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end test "not updating the index frequency will not re-schedule the indexing task" do - channel = channel_fixture() + source = source_fixture() update_attrs = %{name: "some updated name"} - assert {:ok, %Channel{}} = MediaSource.update_channel(channel, update_attrs) - refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) + assert {:ok, %Channel{}} = MediaSource.update_source(source, update_attrs) + refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => source.id}) end test "updates with invalid data returns error changeset" do - channel = channel_fixture() + source = source_fixture() assert {:error, %Ecto.Changeset{}} = - MediaSource.update_channel(channel, @invalid_channel_attrs) + MediaSource.update_source(source, @invalid_source_attrs) - assert channel == MediaSource.get_channel!(channel.id) + assert source == MediaSource.get_source!(source.id) end end - describe "delete_channel/1" do - test "it deletes the channel" do - channel = channel_fixture() - assert {:ok, %Channel{}} = MediaSource.delete_channel(channel) - assert_raise Ecto.NoResultsError, fn -> MediaSource.get_channel!(channel.id) end + describe "delete_source/1" do + test "it deletes the source" do + source = source_fixture() + assert {:ok, %Channel{}} = MediaSource.delete_source(source) + assert_raise Ecto.NoResultsError, fn -> MediaSource.get_source!(source.id) end end - test "it returns a channel changeset" do - channel = channel_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel(channel) + test "it returns a source changeset" do + source = source_fixture() + assert %Ecto.Changeset{} = MediaSource.change_source(source) end test "deletion also deletes all associated tasks" do - channel = channel_fixture() - task = task_fixture(channel_id: channel.id) + source = source_fixture() + task = task_fixture(source_id: source.id) - assert {:ok, %Channel{}} = MediaSource.delete_channel(channel) + assert {:ok, %Channel{}} = MediaSource.delete_source(source) assert_raise Ecto.NoResultsError, fn -> Repo.reload!(task) end end end - describe "change_channel/2" do + describe "change_source/2" do test "it returns a changeset" do - channel = channel_fixture() + source = source_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel(channel) + assert %Ecto.Changeset{} = MediaSource.change_source(source) end end - describe "change_channel_from_url/2" do + describe "change_source_from_url/2" do test "it returns a changeset" do stub(YtDlpRunnerMock, :run, &runner_function_mock/3) - channel = channel_fixture() + source = source_fixture() - assert %Ecto.Changeset{} = MediaSource.change_channel_from_url(channel, %{}) + assert %Ecto.Changeset{} = MediaSource.change_source_from_url(source, %{}) end - test "it does not fetch channel details if the original_url isn't in the changeset" do + test "it does not fetch source details if the original_url isn't in the changeset" do expect(YtDlpRunnerMock, :run, 0, &runner_function_mock/3) - changeset = MediaSource.change_channel_from_url(%Channel{}, %{name: "some updated name"}) + changeset = MediaSource.change_source_from_url(%Channel{}, %{name: "some updated name"}) assert %Ecto.Changeset{} = changeset end - test "it fetches channel details if the original_url is in the changeset" do + test "it fetches source details if the original_url is in the changeset" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Channel{}, %{ original_url: "https://www.youtube.com/channel/abc123" }) assert %Ecto.Changeset{} = changeset end - test "it adds channel details to the changeset, keeping the orignal details" do + test "it adds source details to the changeset, keeping the orignal details" do expect(YtDlpRunnerMock, :run, &runner_function_mock/3) media_profile = media_profile_fixture() media_profile_id = media_profile.id changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Channel{}, %{ original_url: "https://www.youtube.com/channel/abc123", media_profile_id: media_profile.id }) assert %Ecto.Changeset{} = changeset - assert String.starts_with?(changeset.changes.channel_id, "some_channel_id_") + assert String.starts_with?(changeset.changes.collection_id, "some_source_id_") assert %{ name: "some name", @@ -292,7 +296,7 @@ defmodule Pinchflat.MediaSourceTest do end) changeset = - MediaSource.change_channel_from_url(%Channel{}, %{ + MediaSource.change_source_from_url(%Channel{}, %{ original_url: "https://www.youtube.com/channel/abc123" }) @@ -306,7 +310,7 @@ defmodule Pinchflat.MediaSourceTest do :ok, Phoenix.json_library().encode!(%{ channel: "some name", - channel_id: "some_channel_id_#{:rand.uniform(1_000_000)}" + channel_id: "some_source_id_#{:rand.uniform(1_000_000)}" }) } end diff --git a/test/pinchflat/media_test.exs b/test/pinchflat/media_test.exs index d63c77d..2901e7f 100644 --- a/test/pinchflat/media_test.exs +++ b/test/pinchflat/media_test.exs @@ -31,14 +31,14 @@ defmodule Pinchflat.MediaTest do describe "list_pending_media_items_for/1" do test "it returns pending media_items for a given channel" do - channel = channel_fixture() + source = source_fixture() media_item = media_item_fixture(%{channel_id: channel.id, media_filepath: nil}) assert Media.list_pending_media_items_for(channel) == [media_item] end test "it does not return media_items with media_filepath" do - channel = channel_fixture() + source = source_fixture() _media_item = media_item_fixture(%{ @@ -63,7 +63,7 @@ defmodule Pinchflat.MediaTest do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: channel_fixture().id + source_id: source_fixture().id } assert {:ok, %MediaItem{} = media_item} = Media.create_media_item(valid_attrs) @@ -85,7 +85,7 @@ defmodule Pinchflat.MediaTest do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: channel_fixture().id + source_id: source_fixture().id } assert {:ok, %MediaItem{} = media_item} = Media.update_media_item(media_item, update_attrs) diff --git a/test/pinchflat/tasks/channel_tasks_test.exs b/test/pinchflat/tasks/channel_tasks_test.exs index 2314523..d244774 100644 --- a/test/pinchflat/tasks/channel_tasks_test.exs +++ b/test/pinchflat/tasks/channel_tasks_test.exs @@ -10,7 +10,7 @@ defmodule Pinchflat.Tasks.ChannelTasksTest do describe "kickoff_indexing_task/1" do test "it does not schedule a job if the interval is <= 0" do - channel = channel_fixture(index_frequency_minutes: -1) + source = source_fixture(index_frequency_minutes: -1) assert {:ok, :should_not_index} = ChannelTasks.kickoff_indexing_task(channel) @@ -18,7 +18,7 @@ defmodule Pinchflat.Tasks.ChannelTasksTest do end test "it schedules a job if the interval is > 0" do - channel = channel_fixture(index_frequency_minutes: 1) + source = source_fixture(index_frequency_minutes: 1) assert {:ok, _} = ChannelTasks.kickoff_indexing_task(channel) @@ -26,7 +26,7 @@ defmodule Pinchflat.Tasks.ChannelTasksTest do end test "it creates and attaches a task if the interval is > 0" do - channel = channel_fixture(index_frequency_minutes: 1) + source = source_fixture(index_frequency_minutes: 1) assert {:ok, %Task{} = task} = ChannelTasks.kickoff_indexing_task(channel) @@ -34,7 +34,7 @@ defmodule Pinchflat.Tasks.ChannelTasksTest do end test "it deletes any pending tasks for the channel" do - channel = channel_fixture() + source = source_fixture() task = task_fixture(channel_id: channel.id) assert {:ok, _} = ChannelTasks.kickoff_indexing_task(channel) diff --git a/test/pinchflat/tasks_test.exs b/test/pinchflat/tasks_test.exs index fbcc670..e559e46 100644 --- a/test/pinchflat/tasks_test.exs +++ b/test/pinchflat/tasks_test.exs @@ -86,7 +86,7 @@ defmodule Pinchflat.TasksTest do test "accepts a job and channel" do job = job_fixture() - channel = channel_fixture() + source = source_fixture() assert {:ok, %Task{} = task} = Tasks.create_task(job, channel) @@ -115,7 +115,7 @@ defmodule Pinchflat.TasksTest do end test "it creates a task record if successful" do - channel = channel_fixture() + source = source_fixture() assert {:ok, %Task{} = task} = Tasks.create_job_with_task(TestJobWorker.new(%{}), channel) @@ -123,7 +123,7 @@ defmodule Pinchflat.TasksTest do end test "it returns an error if the job already exists" do - channel = channel_fixture() + source = source_fixture() job = TestJobWorker.new(%{foo: "bar"}, unique: [period: :infinity]) assert {:ok, %Task{}} = Tasks.create_job_with_task(job, channel) @@ -131,7 +131,7 @@ defmodule Pinchflat.TasksTest do end test "it returns an error if the job fails to enqueue" do - channel = channel_fixture() + source = source_fixture() assert {:error, %Ecto.Changeset{}} = Tasks.create_job_with_task(%Ecto.Changeset{}, channel) end @@ -156,7 +156,7 @@ defmodule Pinchflat.TasksTest do describe "delete_tasks_for/1" do test "it deletes tasks attached to a channel" do - channel = channel_fixture() + source = source_fixture() task = task_fixture(channel_id: channel.id) assert :ok = Tasks.delete_tasks_for(channel) @@ -174,7 +174,7 @@ defmodule Pinchflat.TasksTest do describe "delete_pending_tasks_for/1" do test "it deletes pending tasks attached to a channel" do - channel = channel_fixture() + source = source_fixture() task = task_fixture(channel_id: channel.id) assert :ok = Tasks.delete_pending_tasks_for(channel) @@ -182,7 +182,7 @@ defmodule Pinchflat.TasksTest do end test "it does not delete non-pending tasks" do - channel = channel_fixture() + source = source_fixture() task = Repo.preload(task_fixture(channel_id: channel.id), :job) :ok = Oban.cancel_job(task.job) diff --git a/test/pinchflat/workers/media_indexing_worker_test.exs b/test/pinchflat/workers/media_indexing_worker_test.exs index dd044c5..c5370ad 100644 --- a/test/pinchflat/workers/media_indexing_worker_test.exs +++ b/test/pinchflat/workers/media_indexing_worker_test.exs @@ -15,7 +15,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it does not do any indexing if the channel shouldn't be indexed" do expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: -1) + source = source_fixture(index_frequency_minutes: -1) perform_job(MediaIndexingWorker, %{id: channel.id}) end @@ -23,7 +23,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it does not reschedule if the channel shouldn't be indexed" do expect(YtDlpRunnerMock, :run, 0, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: -1) + source = source_fixture(index_frequency_minutes: -1) perform_job(MediaIndexingWorker, %{id: channel.id}) refute_enqueued(worker: MediaIndexingWorker, args: %{"id" => channel.id}) @@ -32,7 +32,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it indexes the channel if it should be indexed" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) perform_job(MediaIndexingWorker, %{id: channel.id}) end @@ -40,7 +40,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it kicks off a download job for each pending media item" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1"} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) perform_job(MediaIndexingWorker, %{id: channel.id}) assert [_] = all_enqueued(worker: VideoDownloadWorker) @@ -49,7 +49,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do 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) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) media_item_fixture(%{channel_id: channel.id, media_filepath: nil}) perform_job(MediaIndexingWorker, %{id: channel.id}) @@ -59,7 +59,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do 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) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) perform_job(MediaIndexingWorker, %{id: channel.id}) # Only one job should be enqueued, since the second video is a duplicate @@ -69,7 +69,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it reschedules the job based on the index frequency" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) perform_job(MediaIndexingWorker, %{id: channel.id}) assert_enqueued( @@ -82,7 +82,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it creates a task for the rescheduled job" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, ""} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) task_count_fetcher = fn -> Enum.count(Tasks.list_tasks()) end assert_changed([from: 0, to: 1], task_count_fetcher, fn -> @@ -93,7 +93,7 @@ defmodule Pinchflat.Workers.MediaIndexingWorkerTest do test "it creates the basic media_item records" do expect(YtDlpRunnerMock, :run, fn _url, _opts, _ot -> {:ok, "video1\nvideo2"} end) - channel = channel_fixture(index_frequency_minutes: 10) + source = source_fixture(index_frequency_minutes: 10) media_item_fetcher = fn -> channel diff --git a/test/pinchflat_web/controllers/channel_controller_test.exs b/test/pinchflat_web/controllers/channel_controller_test.exs index 5e71f11..604bb44 100644 --- a/test/pinchflat_web/controllers/channel_controller_test.exs +++ b/test/pinchflat_web/controllers/channel_controller_test.exs @@ -58,7 +58,7 @@ defmodule PinchflatWeb.ChannelControllerTest do end describe "edit channel" do - setup [:create_channel] + setup [:create_source] test "renders form for editing chosen channel", %{conn: conn, channel: channel} do conn = get(conn, ~p"/media_sources/channels/#{channel}/edit") @@ -67,7 +67,7 @@ defmodule PinchflatWeb.ChannelControllerTest do end describe "update channel" do - setup [:create_channel] + setup [:create_source] test "redirects when data is valid", %{conn: conn, channel: channel, update_attrs: update_attrs} do expect(YtDlpRunnerMock, :run, 1, &runner_function_mock/3) @@ -90,7 +90,7 @@ defmodule PinchflatWeb.ChannelControllerTest do end describe "delete channel" do - setup [:create_channel] + setup [:create_source] test "deletes chosen channel", %{conn: conn, channel: channel} do conn = delete(conn, ~p"/media_sources/channels/#{channel}") @@ -102,8 +102,8 @@ defmodule PinchflatWeb.ChannelControllerTest do end end - defp create_channel(_) do - channel = channel_fixture() + defp create_source(_) do + source = source_fixture() %{channel: channel} end diff --git a/test/support/fixtures/media_fixtures.ex b/test/support/fixtures/media_fixtures.ex index 6d0d727..cdf675e 100644 --- a/test/support/fixtures/media_fixtures.ex +++ b/test/support/fixtures/media_fixtures.ex @@ -16,7 +16,7 @@ defmodule Pinchflat.MediaFixtures do media_id: Faker.String.base64(12), title: Faker.Commerce.product_name(), media_filepath: "/video/#{Faker.File.file_name(:video)}", - channel_id: MediaSourceFixtures.channel_fixture().id + channel_id: MediaSourceFixtures.source_fixture().id }) |> Pinchflat.Media.create_media_item() diff --git a/test/support/fixtures/media_source_fixtures.ex b/test/support/fixtures/media_source_fixtures.ex index 007c9d4..408970d 100644 --- a/test/support/fixtures/media_source_fixtures.ex +++ b/test/support/fixtures/media_source_fixtures.ex @@ -9,15 +9,16 @@ defmodule Pinchflat.MediaSourceFixtures do alias Pinchflat.MediaSource.Channel @doc """ - Generate a channel. + Generate a source. """ - def channel_fixture(attrs \\ %{}) do + def source_fixture(attrs \\ %{}) do {:ok, channel} = %Channel{} |> Channel.changeset( Enum.into(attrs, %{ name: "Channel ##{:rand.uniform(1_000_000)}", - channel_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")), + collection_id: Base.encode16(:crypto.hash(:md5, "#{:rand.uniform(1_000_000)}")), + collection_type: "channel", original_url: "https://www.youtube.com/channel/#{Faker.String.base64(12)}", media_profile_id: ProfilesFixtures.media_profile_fixture().id, index_frequency_minutes: 60 diff --git a/test/support/fixtures/tasks_fixtures.ex b/test/support/fixtures/tasks_fixtures.ex index c310ea6..7bb1121 100644 --- a/test/support/fixtures/tasks_fixtures.ex +++ b/test/support/fixtures/tasks_fixtures.ex @@ -14,7 +14,7 @@ defmodule Pinchflat.TasksFixtures do {:ok, task} = attrs |> Enum.into(%{ - channel_id: MediaSourceFixtures.channel_fixture().id, + source_id: MediaSourceFixtures.source_fixture().id, job_id: JobFixtures.job_fixture().id }) |> Pinchflat.Tasks.create_task()