got media source test back working

This commit is contained in:
Kieran Eglin 2024-02-01 21:05:13 -08:00
parent d336f6f46c
commit d54f9f8a79
No known key found for this signature in database
GPG key ID: 193984967FCF432D
21 changed files with 197 additions and 190 deletions

View file

@ -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

View file

@ -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"),

View file

@ -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 """

View file

@ -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
})
)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.")

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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()