CDP.ex

mocks and tdd, 09 Nov 2021

Context

HTTP Live Streaming (also known as HLS) is an HTTP-based adaptive bitrate streaming communications protocol developed by Apple Inc. and released in 2009.

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=299147,AVERAGE-BANDWIDTH=290400,CODECS="avc1.66.30,mp4a.40.2",RESOLUTION=416x234,FRAME-RATE=14.985,AUDIO="PROGRAM_AUDIO"
stream_416x234_200k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1020588,AVERAGE-BANDWIDTH=985600,CODECS="avc1.77.30,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=29.970,AUDIO="PROGRAM_AUDIO"
stream_640x360_800k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1478083,AVERAGE-BANDWIDTH=1425600,CODECS="avc1.4d4029,mp4a.40.2",RESOLUTION=854x480,FRAME-RATE=29.970,AUDIO="PROGRAM_AUDIO"
stream_854x480_1200k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3915128,AVERAGE-BANDWIDTH=3770800,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970,AUDIO="PROGRAM_AUDIO"
stream_1280x720_3300k.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-PROGRAM-DATE-TIME:2021-06-10T06:13:16.934Z
#EXTINF:6.00600,
b/stream_1280x720_3300k/00000/stream_1280x720_3300k_00002.ts
#EXTINF:6.00600,
b/stream_1280x720_3300k/00000/stream_1280x720_3300k_00003.ts
#EXTINF:6.00600,
b/stream_1280x720_3300k/00000/stream_1280x720_3300k_00004.ts
#EXTINF:6.00600,
#EXT-X-ENDLIST
master.m3u8
child.m3u8
commit 521ccb898b55591a498154d4a68c79695446fdeb (HEAD -> master, origin/master)
Author: Philip Giuliani
Date:   Mon Nov 8 15:25:05 2021 +0100

    Use conn.assigns.host

commit c86ed1baf5b594b5dfe77d641f9c83d9e9631f1d
Author: Awlex
Date:   Mon Nov 8 11:50:03 2021 +0100

    unflakify test [closes #20]
...

commit 7a7f5e7fa6a372cf1df3558bdd8a38ba5d7b6f2e
Author: Daniel Morandini
Date:   Mon Sep 27 10:20:41 2021 +0200

    Test responses when a token is provided

commit eb3414441c492c421f4e02e180d6313dac9e66df
Author: Daniel Morandini
Date:   Thu Sep 23 18:52:11 2021 +0200

    Add mocked master playlist fetching

commit b888fd2e9e068c55cf9718a5298d47651af79f0d
Author: Daniel Morandini
Date:   Thu Sep 23 17:11:58 2021 +0200

    Add health_check and token generation

commit b7cbc609c95c2f268df1f91ed8d114967213eb80
Author: Daniel Morandini
Date:   Thu Sep 23 11:38:26 2021 +0200

    Initialize repository

CDP protects, manipulates and caches video.taxi's resources

TLDR;

Terminal Time

Development Approach

TDD (or BDD?) on Plug.Conn

test "does know about 404s" do
  conn = conn(:get, "/this+does_not_exist")
  conn = CDP.Router.call(conn, opts())

  assert conn.state == :sent
  assert conn.status == 404
end

describe "/health_check" do
  test "is healthy" do
    conn = conn(:get, "/health_check")
    conn = CDP.Router.call(conn, opts())

    assert conn.state == :sent
    assert conn.status == 200
    assert conn.resp_body == "OK"
  end
end
cdp.ex

Mocking

you don't want to use it while doing automated tests!

defmodule CDP.File.Store do
  @type get_func :: (CDP.File.Request.t() -> {:ok, binary} | {:error, binary} | {:error, :not_found})
  
  @spec fetch(get_func, CDP.File.Request.t()) :: {:ok, binary} | {:error, :not_found} | {:error, binary}
  
  def fetch(driver, req) do
    case driver.(req) do
      {:error, reason} when is_binary(reason) -> {:error, reason}
      {:error, :not_found} -> {:error, :not_found}
      {:ok, data} -> {:ok, data}
    end
  end
end
NOTE: driver is the function resposible for actually fetching the content!
lib/cdp/file/store.ex
def fetch_playlist(conn, opts, playlist) do
  func = Keyword.fetch!(opts, :storage_get_func)

  playlist =
    conn.path_info
    |> List.insert_at(1, conn.assigns.meta.pipeline)
    |> CDP.M3U8.replace_last(playlist)

  request = %CDP.File.Request{
    host: conn.assigns.host,
    path_info: playlist
  }

  CDP.File.Store.fetch(func, request)
end
NOTE: storage_get_function is configurable!
def init(override \\ []) do
  std = [storage_get_func: &CDP.File.S3.get/1]
  Keyword.merge(std, override)
end
lib/cdp/m3u8/master.ex
defmodule Support.File.Store do
  def get(%CDP.File.Request{host: host, path_info: path}) do
    user = get_user(host)

    [user | path]
    |> Path.join()
    |> get_path()
  end

  def get_path(path) do
    resp =
      (["test", "mocks"] ++ [path])
      |> Path.join()
      |> File.read()

    case resp do
      {:ok, file} -> {:ok, file}
      {:error, :enoent} -> {:error, :not_found}
    end
  end

  defp get_user(host) do
    [h | _] = String.split(host, ".")
    h
  end
end
test/support/storage.ex
NOTE: full mock implementation!
test "returns the playlist for events w/o <meta>.json file" do
  name = "master-nometa.m3u8"
  {:ok, playlist} = Store.get_path("user_id/event_id/a/#{name}")

  conn = conn(:get, "http://user_id.cdn.video.taxi/event_id/#{name}")
  conn = CDP.Router.call(conn, [storage_get_func: &Support.File.Store.get/1])
  assert conn.state == :sent
  assert conn.status == 200
  assert clean_master(conn.resp_body) == playlist
end
test/cdp/router_test.exs
NOTE: mocked storage function is provided!

cdp.ex

By Daniel Morandini

cdp.ex

  • 442