From 44d43ce636884a1da8cb4ca36626493f170aeb06 Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Fri, 19 Jan 2024 15:32:21 -0800 Subject: [PATCH] Added JSON runner; Added test script for CLI interactions --- config/config.exs | 5 +- .../yt_dlp/command_runner.ex | 17 ++++-- .../yt_dlp/command_runner_test.exs | 57 +++++++++++++++---- test/support/scripts/mock-yt-dlp-repeater.sh | 7 +++ 4 files changed, 71 insertions(+), 15 deletions(-) create mode 100755 test/support/scripts/mock-yt-dlp-repeater.sh diff --git a/config/config.exs b/config/config.exs index 5a7d4e9..c3cf761 100644 --- a/config/config.exs +++ b/config/config.exs @@ -9,7 +9,10 @@ import Config config :pinchflat, ecto_repos: [Pinchflat.Repo], - generators: [timestamp_type: :utc_datetime] + generators: [timestamp_type: :utc_datetime], + backend_executables: %{ + yt_dlp: "false" + } # Configures the endpoint config :pinchflat, PinchflatWeb.Endpoint, diff --git a/lib/pinchflat/downloader_backends/yt_dlp/command_runner.ex b/lib/pinchflat/downloader_backends/yt_dlp/command_runner.ex index 134256f..f887bd7 100644 --- a/lib/pinchflat/downloader_backends/yt_dlp/command_runner.ex +++ b/lib/pinchflat/downloader_backends/yt_dlp/command_runner.ex @@ -10,17 +10,26 @@ defmodule Pinchflat.DownloaderBackends.YtDlp.CommandRunner do TODO: look into using a behavior for this (if I ever add other backends) """ - def run(url, command_options, config_options \\ []) do - default_command = System.find_executable("yt-dlp") - base_command = Keyword.get(config_options, :base_command, default_command) + def run(url, command_options) do + command = Application.get_env(:pinchflat, :backend_executables)[:yt_dlp] formatted_command_options = parse_options(command_options) ++ [url] - case System.cmd(base_command, formatted_command_options, stderr_to_stdout: true) do + case System.cmd(command, formatted_command_options, stderr_to_stdout: true) do {output, 0} -> {:ok, output} {output, status} -> {:error, output, status} end end + @doc """ + Runs a yt-dlp command and returns the output as a JSON object + """ + def run_json(url, command_options) do + case run(url, command_options ++ [:dump_json]) do + {:ok, output} -> {:ok, Phoenix.json_library().decode!(output)} + res -> res + end + end + # We want to satisfy the following behaviours: # # 1. If the key is an atom, convert it to a string and convert it to kebab case (for convenience) diff --git a/test/pinchflat/downloader_backends/yt_dlp/command_runner_test.exs b/test/pinchflat/downloader_backends/yt_dlp/command_runner_test.exs index a4a2874..9b80710 100644 --- a/test/pinchflat/downloader_backends/yt_dlp/command_runner_test.exs +++ b/test/pinchflat/downloader_backends/yt_dlp/command_runner_test.exs @@ -3,34 +3,43 @@ defmodule Pinchflat.DownloaderBackends.YtDlp.CommandRunnerTest do alias Pinchflat.DownloaderBackends.YtDlp.CommandRunner, as: Runner - @cmd "echo" + @cmd Path.join([File.cwd!(), "/test/support/scripts/mock-yt-dlp-repeater.sh"]) @video_url "https://www.youtube.com/watch?v=9bZkp7q19f0" + @original_executables Application.compile_env(:pinchflat, :backend_executables) - describe "run/3" do + setup do + Application.put_env(:pinchflat, :backend_executables, %{@original_executables | yt_dlp: @cmd}) + + on_exit(fn -> + Application.put_env(:pinchflat, :backend_executables, @original_executables) + end) + end + + describe "run/2" do test "it returns the output and status when the command succeeds" do - assert {:ok, _output} = Runner.run(@video_url, [], base_command: @cmd) + assert {:ok, _output} = Runner.run(@video_url, []) end test "it converts symbol k-v arg keys to kebab case" do - assert {:ok, output} = Runner.run(@video_url, [buffer_size: 1024], base_command: @cmd) + assert {:ok, output} = Runner.run(@video_url, buffer_size: 1024) assert String.contains?(output, "--buffer-size 1024") end test "it keeps string k-v arg keys untouched" do - assert {:ok, output} = Runner.run(@video_url, [{"--under_score", 1024}], base_command: @cmd) + assert {:ok, output} = Runner.run(@video_url, [{"--under_score", 1024}]) assert String.contains?(output, "--under_score 1024") end test "it converts symbol arg keys to kebab case" do - assert {:ok, output} = Runner.run(@video_url, [:ignore_errors], base_command: @cmd) + assert {:ok, output} = Runner.run(@video_url, [:ignore_errors]) assert String.contains?(output, "--ignore-errors") end test "it keeps string arg keys untouched" do - assert {:ok, output} = Runner.run(@video_url, ["-v"], base_command: @cmd) + assert {:ok, output} = Runner.run(@video_url, ["-v"]) assert String.contains?(output, "-v") refute String.contains?(output, "--v") @@ -38,19 +47,47 @@ defmodule Pinchflat.DownloaderBackends.YtDlp.CommandRunnerTest do test "it places arg keys (flags) at the beginning of the command" do assert {:ok, output} = - Runner.run(@video_url, [{"--under_score", 1024}, :ignore_errors], base_command: @cmd) + Runner.run(@video_url, [{"--under_score", 1024}, :ignore_errors]) assert String.contains?(output, "--ignore-errors --under_score 1024") end test "it includes the video url as the last argument" do - assert {:ok, output} = Runner.run(@video_url, [:ignore_errors], base_command: @cmd) + assert {:ok, output} = Runner.run(@video_url, [:ignore_errors]) assert String.contains?(output, "--ignore-errors #{@video_url}\n") end test "it returns the output and status when the command fails" do - assert {:error, "", 1} = Runner.run(@video_url, [], base_command: "/bin/false") + Application.put_env(:pinchflat, :backend_executables, %{ + @original_executables + | yt_dlp: "/bin/false" + }) + + assert {:error, "", 1} = Runner.run(@video_url, []) + end + end + + describe "run_json/2" do + test "it returns decoded JSON when the command succeeds" do + assert {:ok, output} = Runner.run_json(@video_url, []) + + assert is_map(output) + end + + test "it adds the --dump-json flag automatically" do + assert {:ok, %{"args" => output}} = Runner.run_json(@video_url, []) + + assert String.contains?(output, "--dump-json") + end + + test "it returns errors when the command fails" do + Application.put_env(:pinchflat, :backend_executables, %{ + @original_executables + | yt_dlp: "/bin/false" + }) + + assert {:error, "", 1} = Runner.run_json(@video_url, []) end end end diff --git a/test/support/scripts/mock-yt-dlp-repeater.sh b/test/support/scripts/mock-yt-dlp-repeater.sh new file mode 100755 index 0000000..169303d --- /dev/null +++ b/test/support/scripts/mock-yt-dlp-repeater.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if [[ "$@" == *"--dump-json"* ]]; then + echo '{ "args": "'$@'"}' +else + echo $@ +fi