Cleaned up and re-enabled back tests

Cleaned up and re-enabled back tests

Code.require_file "../test_helper.exs", __ENV__.file

defmodule RadpathTests.RadpathFacts do

  # use Amrita.Sweet
  # use ExUnit.Case, async: true
  use ExUnit.Case
  use PatternTap

  import PathHelpers
    import Path, only: [basename: 1]
  import Enum, only: [map: 2]

  alias :file, as: F

  defmodule ZippingTest do

    use ExUnit.Case
    def path_exists(path) do
        assert File.exists?(path)
    end

    defmodule ZipTest do
      use ExUnit.Case
      try do
        Path.join(fixture_path, "Testdir1.zip") |> File.exists? |> refute
        Radpath.zip([fixture_path], "Testdir1.zip")
        # "Testdir1.zip" |> File.exists?
        "Testdir1.zip" |> File.exists?
        # System.cmd("zip",["-T","Testdir1.zip"])
        # |> {"test of Testdir1.zip OK\n", 0}
        # System.cmd("zipinfo",["-1","Testdir1.zip"])
        # |> tap({result_str, _} ~> result_str)
        # |> contains "testdir1"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    defmodule TestZipOnePath  do
       use ExUnit.Case

      dir = Path.join(fixture_path, "testdir2")

      try do
        Path.join(fixture_path, "Testdir2.zip") |> File.exists? |> refute
        Radpath.zip([dir], "Testdir2.zip")
        # "Testdir2.zip" |> File.exists? |> assert
        assert File.exists? "Testdir2.zip"
        # System.cmd("zip",["-T","Testdir2.zip"])
        # |> tap({result_str, _} ~> result_str)
        # |> String.strip |> "test of Testdir2.zip OK"
        # System.cmd("zipinfo",["-1","Testdir2.zip"])
        # |> tap({result_str2, _} ~> result_str2)
        # |> contains "testdir2"
      after
        File.rm_rf("Testdir2.zip")
      end
    end
  end

  defmodule TestZipOneBitstringPath do
    use ExUnit.Case
    dir = Path.join(fixture_path, "testdir1")
    try do
      Path.join(fixture_path, "Testdir3.zip") |> File.exists? |> refute
      Radpath.zip(dir, "Testdir3.zip")
      # "Testdir3.zip" |> File.exists?
      assert File.exists? "Testdir3.zip"
      # System.cmd("zip",["-T","Testdir3.zip"]) |>
      #           {"test of Testdir3.zip OK\n", 0}
      # System.cmd("zipinfo",["-1","Testdir3.zip"])
      # |> tap({result_str, _} ~> result_str)
      # |> contains "testdir1"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  defmodule TestUnzipOneBitstringPathInCWD do

    use ExUnit.Case
    try do
      Path.join(fixture_path, "dome.csv") |> File.exists? |> refute
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), fixture_path)
      # Path.join(fixture_path, "dome.csv") |> File.exists?
      assert File.exists? Path.join(fixture_path, "dome.csv")
    after
      File.rm_rf(Path.join(fixture_path, "dome.csv"))
    end
  end

  defmodule TestunzipOnebitstringPathIntmp do
  use ExUnit.Case
    try do
      Path.join(fixture_path, "dome.csv") |> File.exists? |> refute
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), "/tmp")
    after
      # Path.join("/tmp", "dome.csv") |> File.exists?
      assert File.exists? Path.join("/tmp", "dome.csv")
  end
  defmodule TestZipPathThatDoesNotExist do
  use ExUnit.Case
    try do
        Path.join(fixture_path, "Testdir-dont-exist.zip") |>
                   File.exists? |> refute
        Radpath.zip(["/gogo/I/don/exist"], "Testdir-dont-exist.zip")
            after
        "Testdir-dont-exist.zip" |> File.exists? |> refute
      rescue
        e in RuntimeError -> e
      end
    end
  end

  defmodule FilteringTest do
  use ExUnit.Case

    @dud_files ["testfile1.dud, file3.dud"]
    @test_files ["testdir3", "testdir2", "testdir1"]
    @file_list ["file1.txt", "file2.txt", "file3.log"]
    @file_ext ["txt", "log"]
    @long_file_list @file_list ++ ["testfile1.txt",
                                   "testfile2.txt",
                                   "testfile3.txt"]

    test "File Filtering" do
       files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
       Enum.map(@dud_files, fn(x) -> refute Enum.member?(files, x) end)
       Enum.all?(@dud_files -- ["file3.log"], fn(x) -> files |> Enum.member? x end)
    end

    test "File Filtering returns empty if path does not exist" do
      assert Radpath.files("/this/path/does/not/exist") == []
    end

    test "Directory filtering" do
       dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
       map(["testfile1.dud, file3.dud"], fn(x) -> refute Enum.member?(dirs, x) end)
       Enum.all?(["testdir3", "testdir2", "testdir1"], fn(x) -> Enum.member?(dirs, x) end)
    end

    test "Directory Filter: List" do
        expected = @test_files ++ ["fixtures"] |> Enum.sort
        actual = Radpath.dirs(["test", "lib"]) |> map(&basename(&1))
        assert actual == expected ++ ["Radpath"]
    end

    test "Directory Filtering: Regex" do
        actual = Radpath.dirs("test", "fixtures") |> map(&basename(&1))
	assert actual == ["fixtures"]
    end

    test "Filtering Files" do
        Radpath.files(fixture_path, "log") |> map(&basename(&1)) |> (&(assert &1 == ["file3.log"])).()
    end

    test "Filtering Files with Lists" do
       Radpath.files(["test", "lib"], @file_ext) |> map(&basename(&1)) |> Enum.sort |> (&(assert &1 == @long_file_list)).()
    end

    test "Filtering with Expanded Paths" do
       Radpath.files("test/fixtures", "log") |> map(&basename(&1)) |> (&(assert &1 == ["file3.log"])).()
    end

    test "Filtering Multiple filter for Files" do
       files = Radpath.files(fixture_path, @file_ext) |> map(&basename(&1))
       files |> Enum.sort |> (&(assert &1 == @long_file_list)).()
       Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end

    test "Filtering long file list" do
       files = Radpath.files(fixture_path, @file_ext) |> Enum.map(&Path.basename(&1))
       files |> Enum.sort |> (&(assert &1 == @long_file_list)).()
       Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end

    test "Filtering Multiple Filter for Files" do
      files = Radpath.files(['lib'], @file_ext) |> Enum.map(&Path.basename(&1))
      Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end
  end

  defmodule Testsymlink do

   use ExUnit.Case
   import Radpath, only: [symlink: 2, islink?: 1]
   @src Path.join(fixture_path, "testdir3")
   @dest Path.join(Path.expand("."), "testdir3")

    test "Symlink Normal Usage" do
       try do
         refute File.exists? @dest
         @dest |> File.exists? |> refute
         symlink(@src, @dest)
         {result, link} = F.read_link(@dest)
         assert {result, basename(link)} == {:ok, "testdir3"}
         assert islink?(@dest)
       after
         File.rm_rf @dest
       end
    end

    test "Symlink for Non Existent" do
      src = Path.join(fixture_path, "testdir3xx")
      try do
        Path.join(Path.expand("."), "testdir3") |> File.exists? |> refute
        symlink(src, @dest)
        assert {:error, :enoent} == F.read_link(@dest)
        Path.join(Path.expand("."), "testdir3") |> File.exists? |> refute
      after
        File.rm_rf Path.join(Path.expand("."), "testdir3")
      end
    end

    test "Test symlink: islink? Return true if path is symlink" do
      test_dest = Path.join(fixture_path, "test_symlink")
      try do
        test_dest |> File.exists? |> refute
        symlink(fixture_path, test_dest)
        assert islink?(test_dest)
        {result, link} = F.read_link(test_dest)
      after
        File.rm_rf test_dest
      end
    end

    test "Test symlink: islink? Return false if path is not symlink or if does not exist" do
      islink?(fixture_path) |> refute
      islink?("/I/wiLL/neveR/exist") |> refute
    end
   end

  defmodule TestTempfilefs do
    use ExUnit.Case
    test "Test Tempfilefs: Without Argument" do
            tmpdirpath1 = Radpath.mktempdir

        try do
          Radpath.mktempdir |> File.exists?
        after
          File.rm_rf tmpdirpath1
        end
    end

    test "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
            tmpdirpath = Radpath.mktempdir(src)

      try do
        assert File.exists?(tmpdirpath)
      after
        File.rm_rf tmpdirpath
      end
    end

    test "Test mktempdir: Nonexistant parent path, error returned" do
       tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
       try do
         assert tmpdirpath == :enoent
       rescue
         e in RuntimeError -> e
       end
    end

    test "Test mktempfile: No argument" do
      {_, fd, filepath} = Radpath.mktempfile
      IO.write fd, "hulahoop"
      try do
        assert File.exists? filepath
        read_content = File.read! filepath
        assert read_content == "hulahoop"
      after
        File.close filepath
        File.rm_rf filepath
      end
    end

    test "Test mktempfile: With arguments" do
      {_, fd, filepath} = Radpath.mktempfile(".log", "/tmp")
      IO.write fd, "hulahoop with args"
      try do
        read_content = File.read! filepath
        assert Path.extname(filepath) == ".log"
        assert read_content == "hulahoop with args"
      after
        File.close filepath
        File.rm_rf filepath
      end
    end

  end

  defmodule TestRenameandMv do
    use ExUnit.Case
    @source_file "/tmp/hoho.txt"
    @dest_file "/tmp/hehe.txt"

    test "Test rename: Normal Usage" do
        File.write(@source_file, "test rename")
      try do
        @source_file |> File.exists?
        md5sum_source = Radpath.md5sum(@source_file)
	File.cp(@source_file, "/tmp/hihi")
        Radpath.rename(@source_file, @dest_file)
        refute @source_file |> File.exists?
        @dest_file |> File.exists?
        assert Radpath.md5sum(@dest_file) == Radpath.md5sum("/tmp/hihi")
      after
        File.rm_rf @dest_file
      end
    end

    test "Test mv: Normal Usage" do
      File.write("/tmp/hoho.txt", "test mv")
      try do
        "/tmp/hoho.txt" |> File.exists?
        # md5sum_source = Radpath.md5sum(@source_file)
        # original_md5sum = Radpath.md5sum("/tmp/hoho.txt")
	File.cp("/tmp/hoho.txt", "/tmp/hihi.txt")
        Radpath.mv("/tmp/hoho.txt", "/tmp/hehe.txt")
        refute "/tmp/hoho.txt" |> File.exists?
        "/tmp/hehe.txt" |> File.exists?
        assert Radpath.md5sum("/tmp/hehe.txt") == Radpath.md5sum("/tmp/hihi.txt")
      after
        File.rm_rf @dest_file
      end
    end

    test "Test rename: Source file does not exist" do
        refute @source_file |> File.exists?
        # Radpath.rename("/tmp/hoho.txt", "/tmp/hehe.txt")
        Radpath.rename(@source_file, @dest_file)
        refute @source_file |> File.exists?
    end

    test "Test mv: Source file does not exist" do
        refute @source_file |> File.exists?
        Radpath.mv(@source_file, @dest_file)
        refute @dest_file |> File.exists?
    end

    test "Test mv: Destination folder does not exist" do
      source_file = "mix.exs"
      dest_file = "/tmp/xxxyyyzzz/mix.exs"
      assert Radpath.mv(source_file, dest_file) == {:error, :enoent}
    end

  end

  defmodule TestRelativePaths do
  use ExUnit.Case
        import Radpath, only: [relative_path: 2]

    test "Test relative_path: Normal Usage" do
      assert relative_path("/tmp/base", "/tmp/base/hoho.txt") == "hoho.txt"
    end

    test "Test relative_path: If same path is given empty string will result" do
      assert relative_path("/tmp/base/", "/tmp/base/") == ""
    end
  end

  defmodule TestEnsure do
  use ExUnit.Case

    test "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      refute test_dir_path |> File.exists?
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> File.exists?
      after
            assert test_dir_path |> Radpath.parent_path |> Radpath.erusne
      end
    end

    test "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      refute test_file_path |> File.exists?
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> File.exists?
      after
        assert test_file_path |> Radpath.parent_path |> Radpath.erusne
      end
    end
  end

  defmodule TestOtherfunctions do
  use ExUnit.Case
        import Radpath, only: [md5sum: 1, sha1sum: 1, parent_path: 1]
    test "Test md5sum: md5sum function" do
      [h | _]= String.split(to_string(:os.cmd('md5sum mix.exs')))
      assert md5sum("mix.exs") == h
    end
    test "Test sha1sumsha1sum function" do
      [h | _]= String.split(to_string(:os.cmd('sha1sum mix.exs')))
      assert sha1sum("mix.exs") ==  h
    end
    test "Test sha1sum: sha1sum on directory" do
      assert sha1sum("/tmp") == :error
    end
    test "Test md5sum: md5sum on directory" do
      assert md5sum("/tmp") == :error
    end
    test "Test md5sum: md5sum on non existent directory" do
      assert md5sum("xxx") == :error
    end
    test "Test ParentPath: Return parent path of string" do
      assert parent_path("/I/am/long/dir") == "/I/am/long/"
    end
  end
end

Code.require_file "../test_helper.exs", __ENV__.file

defmodule RadpathTests.RadpathFacts do

  # use Amrita.Sweet
  # use ExUnit.Case, async: true
  use ExUnit.Case
  use PatternTap

  import PathHelpers
    import Path, only: [basename: 1]
  import Enum, only: [map: 2]

  alias :file, as: F

  defmodule ZippingTest do

    use ExUnit.Case
    def path_exists(path) do
        assert File.exists?(path)
    end

    defmodule ZipTest do
      use ExUnit.Case
      try do
        Path.join(fixture_path, "Testdir1.zip") |> File.exists? |> refute
        Radpath.zip([fixture_path], "Testdir1.zip")
        # "Testdir1.zip" |> File.exists?
        "Testdir1.zip" |> File.exists?
        # System.cmd("zip",["-T","Testdir1.zip"])
        # |> {"test of Testdir1.zip OK\n", 0}
        # System.cmd("zipinfo",["-1","Testdir1.zip"])
        # |> tap({result_str, _} ~> result_str)
        # |> contains "testdir1"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    defmodule TestZipOnePath  do
       use ExUnit.Case

      dir = Path.join(fixture_path, "testdir2")

      try do
        Path.join(fixture_path, "Testdir2.zip") |> File.exists? |> refute
        Radpath.zip([dir], "Testdir2.zip")
        # "Testdir2.zip" |> File.exists? |> assert
        assert File.exists? "Testdir2.zip"
        # System.cmd("zip",["-T","Testdir2.zip"])
        # |> tap({result_str, _} ~> result_str)
        # |> String.strip |> "test of Testdir2.zip OK"
        # System.cmd("zipinfo",["-1","Testdir2.zip"])
        # |> tap({result_str2, _} ~> result_str2)
        # |> contains "testdir2"
      after
        File.rm_rf("Testdir2.zip")
      end
    end
  end

  defmodule TestZipOneBitstringPath do
    use ExUnit.Case
    dir = Path.join(fixture_path, "testdir1")
    try do
      Path.join(fixture_path, "Testdir3.zip") |> File.exists? |> refute
      Radpath.zip(dir, "Testdir3.zip")
      # "Testdir3.zip" |> File.exists?
      assert File.exists? "Testdir3.zip"
      # System.cmd("zip",["-T","Testdir3.zip"]) |>
      #           {"test of Testdir3.zip OK\n", 0}
      # System.cmd("zipinfo",["-1","Testdir3.zip"])
      # |> tap({result_str, _} ~> result_str)
      # |> contains "testdir1"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  defmodule TestUnzipOneBitstringPathInCWD do

    use ExUnit.Case
    try do
      Path.join(fixture_path, "dome.csv") |> File.exists? |> refute
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), fixture_path)
      # Path.join(fixture_path, "dome.csv") |> File.exists?
      assert File.exists? Path.join(fixture_path, "dome.csv")
    after
      File.rm_rf(Path.join(fixture_path, "dome.csv"))
    end
  end

  defmodule TestunzipOnebitstringPathIntmp do
  use ExUnit.Case
    try do
      Path.join(fixture_path, "dome.csv") |> File.exists? |> refute
      Radpath.unzip(Path.join(fixture_path, "dome.zip"), "/tmp")
    after
      # Path.join("/tmp", "dome.csv") |> File.exists?
      assert File.exists? Path.join("/tmp", "dome.csv")
  end
  defmodule TestZipPathThatDoesNotExist do
  use ExUnit.Case
    try do
        Path.join(fixture_path, "Testdir-dont-exist.zip") |>
                   File.exists? |> refute
        Radpath.zip(["/gogo/I/don/exist"], "Testdir-dont-exist.zip")
            after
        "Testdir-dont-exist.zip" |> File.exists? |> refute
      rescue
        e in RuntimeError -> e
      end
    end
  end

  defmodule FilteringTest do
  use ExUnit.Case

    @dud_files ["testfile1.dud, file3.dud"]
    @test_files ["testdir3", "testdir2", "testdir1"]
    @file_list ["file1.txt", "file2.txt", "file3.log"]
    @file_ext ["txt", "log"]
    @long_file_list @file_list ++ ["testfile1.txt",
                                   "testfile2.txt",
                                   "testfile3.txt"]

    test "File Filtering" do
       files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
       Enum.map(@dud_files, fn(x) -> refute Enum.member?(files, x) end)
       Enum.all?(@dud_files -- ["file3.log"], fn(x) -> files |> Enum.member? x end)
    end

    test "File Filtering returns empty if path does not exist" do
      assert Radpath.files("/this/path/does/not/exist") == []
    end

    test "Directory filtering" do
       dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
       map(["testfile1.dud, file3.dud"], fn(x) -> refute Enum.member?(dirs, x) end)
       Enum.all?(["testdir3", "testdir2", "testdir1"], fn(x) -> Enum.member?(dirs, x) end)
    end

    test "Directory Filter: List" do
        expected = @test_files ++ ["fixtures"] |> Enum.sort
        actual = Radpath.dirs(["test", "lib"]) |> map(&basename(&1))
        assert actual == expected ++ ["Radpath"]
    end

    test "Directory Filtering: Regex" do
        actual = Radpath.dirs("test", "fixtures") |> map(&basename(&1))
	assert actual == ["fixtures"]
    end

    test "Filtering Files" do
        Radpath.files(fixture_path, "log") |> map(&basename(&1)) |> (&(assert &1 == ["file3.log"])).()
    end

    test "Filtering Files with Lists" do
       Radpath.files(["test", "lib"], @file_ext) |> map(&basename(&1)) |> Enum.sort |> (&(assert &1 == @long_file_list)).()
    end

    test "Filtering with Expanded Paths" do
       Radpath.files("test/fixtures", "log") |> map(&basename(&1)) |> (&(assert &1 == ["file3.log"])).()
    end

    test "Filtering Multiple filter for Files" do
       files = Radpath.files(fixture_path, @file_ext) |> map(&basename(&1))
       files |> Enum.sort |> (&(assert &1 == @long_file_list)).()
       Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end

    test "Filtering long file list" do
       files = Radpath.files(fixture_path, @file_ext) |> Enum.map(&Path.basename(&1))
       files |> Enum.sort |> (&(assert &1 == @long_file_list)).()
       Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end

    test "Filtering Multiple Filter for Files" do
      files = Radpath.files(['lib'], @file_ext) |> Enum.map(&Path.basename(&1))
      Enum.all?(@file_list, fn(x) -> Enum.member?(files, x) end)
    end
  end

  defmodule Testsymlink do

   use ExUnit.Case
   import Radpath, only: [symlink: 2, islink?: 1]
   @src Path.join(fixture_path, "testdir3")
   @dest Path.join(Path.expand("."), "testdir3")

    test "Symlink Normal Usage" do
       try do
         refute File.exists? @dest
         symlink(@src, @dest)
         {result, link} = F.read_link(@dest)
         assert {result, basename(link)} == {:ok, "testdir3"}
         assert islink?(@dest)
       after
         File.rm_rf @dest
       end
    end

    test "Symlink for Non Existent" do
      src = Path.join(fixture_path, "testdir3xx")
      try do
        Path.join(Path.expand("."), "testdir3") |> File.exists? |> refute
        symlink(src, @dest)
        assert {:error, :enoent} == F.read_link(@dest)
	# {error, enoent} = F.read_link(@dest) == {:error, :enoent}
	# assert error == :error
	# assert enoent == :enoent
        Path.join(Path.expand("."), "testdir3") |> File.exists? |> refute
      after
        File.rm_rf Path.join(Path.expand("."), "testdir3")
      end
    end

    test "Test symlink: islink? Return true if path is symlink" do
      test_dest = Path.join(fixture_path, "test_symlink")
      try do
        test_dest |> File.exists? |> refute
        symlink(fixture_path, test_dest)
        assert islink?(test_dest)
        {result, link} = F.read_link(test_dest)
        # {result, basename(to_string(link))} |> {:ok, "fixtures"}
      after
        File.rm_rf test_dest
      end
    end

    test "Test symlink: islink? Return false if path is not symlink or if does not exist" do
      islink?(fixture_path) |> refute
      islink?("/I/wiLL/neveR/exist") |> refute
    end
   end

  defmodule TestTempfilefs do
    use ExUnit.Case
    test "Test Tempfilefs: Without Argument" do
            tmpdirpath1 = Radpath.mktempdir

        try do
          Radpath.mktempdir |> File.exists?
        after
          File.rm_rf tmpdirpath1
        end
    end

    test "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
            tmpdirpath = Radpath.mktempdir(src)

      try do
        assert File.exists?(tmpdirpath)
      after
        File.rm_rf tmpdirpath
      end
    end

    # test "Test mktempdir: Nonexistant parent path, error returned" do
    #   tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
    #   try do
    #     tmpdirpath |> :enoent
    #   rescue
    #     e in RuntimeError -> e
    #   end
    # end

    test "Test mktempfile: No argument" do
      {_, fd, filepath} = Radpath.mktempfile
      IO.write fd, "hulahoop"
      try do
        assert File.exists? filepath
        read_content = File.read! filepath
        assert read_content == "hulahoop"
      after
        File.close filepath
        File.rm_rf filepath
      end
    end

    test "Test mktempfile: With arguments" do
      {_, fd, filepath} = Radpath.mktempfile(".log", "/tmp")
      IO.write fd, "hulahoop with args"
      try do
        read_content = File.read! filepath
        assert Path.extname(filepath) == ".log"
        assert read_content == "hulahoop with args"
      after
        File.close filepath
        File.rm_rf filepath
      end
    end

  end

  defmodule TestRenameandMv do
  use ExUnit.Case

    @source_file "/tmp/hoho.txt"
    @dest_file "/tmp/hehe.txt"

    test "Test rename: Normal Usage" do
        File.write(@source_file, "test rename")
      try do
        @source_file |> File.exists?
        md5sum_source = Radpath.md5sum(@source_file)
	File.cp(@source_file, "/tmp/hihi")
        Radpath.rename(@source_file, @dest_file)
        refute @source_file |> File.exists?
        @dest_file |> File.exists?
        assert Radpath.md5sum(@dest_file) == Radpath.md5sum("/tmp/hihi")
      after
        File.rm_rf @dest_file
      end
    end

    test "Test mv: Normal Usage" do
      File.write("/tmp/hoho.txt", "test mv")
      try do
        "/tmp/hoho.txt" |> File.exists?
        # md5sum_source = Radpath.md5sum(@source_file)
        # original_md5sum = Radpath.md5sum("/tmp/hoho.txt")
	File.cp("/tmp/hoho.txt", "/tmp/hihi.txt")
        Radpath.mv("/tmp/hoho.txt", "/tmp/hehe.txt")
        refute "/tmp/hoho.txt" |> File.exists?
        "/tmp/hehe.txt" |> File.exists?
        assert Radpath.md5sum("/tmp/hehe.txt") == Radpath.md5sum("/tmp/hihi.txt")
      after
        File.rm_rf @dest_file
      end
    end

    test "Test rename: Source file does not exist" do
        refute @source_file |> File.exists?
        # Radpath.rename("/tmp/hoho.txt", "/tmp/hehe.txt")
        Radpath.rename(@source_file, @dest_file)
        refute @source_file |> File.exists?
    end

    test "Test mv: Source file does not exist" do
        refute @source_file |> File.exists?
        Radpath.mv(@source_file, @dest_file)
        refute @dest_file |> File.exists?
    end

    test "Test mv: Destination folder does not exist" do
      source_file = "mix.exs"
      dest_file = "/tmp/xxxyyyzzz/mix.exs"
      assert Radpath.mv(source_file, dest_file) == {:error, :enoent}
    end

  end

  defmodule TestRelativePaths do
  use ExUnit.Case
        import Radpath, only: [relative_path: 2]

    test "Test relative_path: Normal Usage" do
      assert relative_path("/tmp/base", "/tmp/base/hoho.txt") == "hoho.txt"
    end

    test "Test relative_path: If same path is given empty string will result" do
      assert relative_path("/tmp/base/", "/tmp/base/") == ""
    end
  end

  defmodule TestEnsure do
  use ExUnit.Case

    test "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      refute test_dir_path |> File.exists?
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> File.exists?
      after
            assert test_dir_path |> Radpath.parent_path |> Radpath.erusne
      end
    end

    test "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      refute test_file_path |> File.exists?
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> File.exists?
      after
        assert test_file_path |> Radpath.parent_path |> Radpath.erusne
      end
    end
  end

  defmodule TestOtherfunctions do
  use ExUnit.Case
        import Radpath, only: [md5sum: 1, sha1sum: 1, parent_path: 1]
    test "Test md5sum: md5sum function" do
      [h | _]= String.split(to_string(:os.cmd('md5sum mix.exs')))
            assert md5sum("mix.exs") == h
    end
    test "Test sha1sumsha1sum function" do
      [h | _]= String.split(to_string(:os.cmd('sha1sum mix.exs')))
      assert sha1sum("mix.exs") ==  h
    end
    test "Test sha1sum: sha1sum on directory" do
      assert sha1sum("/tmp") == :error
    end
    test "Test md5sum: md5sum on directory" do
      assert md5sum("/tmp") == :error
    end
    test "Test md5sum: md5sum on non existent directory" do
      assert md5sum("xxx") == :error
    end
    test "Test ParentPath: Return parent path of string" do
      assert parent_path("/I/am/long/dir") == "/I/am/long/"
    end
  end
end


ENH clean up a bit of the code taking away some redundant calls

Code.require_file "test_helper.exs", __DIR__

defmodule RadpathTestInit do
  use ExUnit.CaseTemplate
  import PathHelpers

  using do
    quote do
      import PathHelpers
    end
  end

  setup do
    File.mkdir_p!(tmp_path)
    :ok
  end

  teardown do
    File.rm_rf(tmp_path)
    :ok
  end
end

defmodule RadpathTests do
  use Amrita.Sweet

  import PathHelpers
  alias :file, as: F
  alias :zip, as: Z

  defchecker path_exists(path) do
    File.exists?(path) |> truthy
  end

  facts "Test Listing" do
    fact "Test listing: Files" do
      files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
      Enum.each(["file1.txt", "file2.txt"], fn(x) -> files |> contains x end)
    end

    fact "Test listing: Directories" do
      dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
      Enum.each(["testdir3", "testdir2", "testdir1"], fn(x) -> dirs |> contains x end)
    end
  end

  facts "Test zipping" do
    fact "Test zip: List of directories" do

      Path.join(fixture_path, "Testdir1.zip") |> ! path_exists
      try do
        Radpath.zip([fixture_path], "Testdir1.zip")
        "Testdir1.zip" |> path_exists
        System.cmd("zip -T Testdir1.zip") |> String.strip |> equals "test of Testdir1.zip OK"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    fact "Test zip: One path" do
      dir = Path.join(fixture_path, "testdir2")
      Path.join(fixture_path, "Testdir2.zip") |> ! path_exists

      try do
        Radpath.zip([dir], "Testdir2.zip")
        "Testdir2.zip" |> path_exists
        System.cmd("zip -T Testdir2.zip") |> String.strip |> equals "test of Testdir2.zip OK"
      after
        File.rm_rf("Testdir2.zip")
      end
    end

  fact "Test zip: One bitstring path" do
    dir = Path.join(fixture_path, "testdir1")
    Path.join(fixture_path, "Testdir3.zip") |> ! path_exists
    try do
      Radpath.zip(dir, "Testdir3.zip")
      "Testdir3.zip" |> path_exists
      System.cmd("zip -T Testdir3.zip") |> String.strip |> equals "test of Testdir3.zip OK"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  fact "Test zip: Path that does not exist" do
      dir = "/gogo/I/don/exist/"
      Path.join(fixture_path, "Testdir-dont-exist.zip") |> ! path_exists

      try do
        Radpath.zip([dir], "Testdir-dont-exist.zip")
        "Testdir-dont-exist.zip" |> ! path_exists
      end
    end
  end

  facts "Test Filtering" do
    fact "Test Filtering: files" do
      Radpath.files(fixture_path, "log") |> Enum.map(&Path.basename(&1)) |> ["file3.log"]
    end

    fact "Test Filtering: Multiple filter for files function" do
      files = Radpath.files(fixture_path, ["log", "txt"]) |> Enum.map(&Path.basename(&1))
      ["file1.txt", "file2.txt", "file3.log"] |> for_all (&Enum.member?(files, &1))
    end
  end

  facts "Test symlink" do
    fact "Test symlink: Normal Usage" do
      src = Path.join(fixture_path, "testdir3")
      dest = Path.join(Path.expand("."), "testdir3")

      try do
        dest |> ! path_exists
        Radpath.symlink(src, dest)
        elem(F.read_link(dest), 0) |> equals :ok
        dest |> path_exists
      after
        File.rm_rf dest
      end
    end

    fact "Test symlink: For non existent src file" do
      src = Path.join(fixture_path, "testdir3xx")
      dest = Path.join(Path.expand("."), "testdir3")

      try do
        dest |> ! path_exists
        Radpath.symlink(src, dest)
        F.read_link(dest) |> equals {:error, :enoent}
        dest |> ! path_exists
      after
        File.rm_rf dest
      end
    end

    fact "Test Symlink: islink? Return true if path is symlink" do

      test_dest = Path.join(fixture_path, "test_symlink")

      try do
        test_dest |> ! path_exists
        Radpath.symlink(fixture_path, test_dest)
        Radpath.islink?(test_dest) |> truthy
      after
        File.rm_rf test_dest
      end
    end

    fact "Test Symlink: islink? Return false if path is symlink" do
      Radpath.islink?(fixture_path) |> falsey
    end

  end

  facts "Test Tempfilefs" do
    fact "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
      tmpdirpath = Radpath.mktempdir(src)
      try do
        tmpdirpath |> path_exists
      after
        File.rm_rf tmpdirpath
      end
    end

    fact "Test mktempdir: Without argument" do
      tmpdirpath1 = Radpath.mktempdir
      tmpdirpath1 |> path_exists
      File.rm_rf tmpdirpath1
    end

    fact "Test mktempdir: Nonexistant parent path, error returned" do
      tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
      try do
        tmpdirpath |> ! path_exists
        elem(tmpdirpath, 0) == :error
      end
    end
  end

  facts "Test Rename" do
    fact "Test Rename: Normal Usage" do
      source_file = "/tmp/hoho.txt"
      dest_file = "/tmp/hehe.txt"
      File.touch!(source_file)
      try do
        source_file |> path_exists
        Radpath.rename(source_file, dest_file)
        source_file |> ! path_exists
        dest_file |> path_exists
      after
        File.rm_rf dest_file
      end
    end

    fact "Test Rename: Source file does not exist" do
      source_file = "/tmp/hoho.txt"
      dest_file = "/tmp/hehe.txt"
      source_file |> ! path_exists
      Radpath.rename(source_file, dest_file)
      dest_file |> ! path_exists
    end
  end

  facts "Test relative paths" do
    fact "Test relative_path: Normal Usage" do
      Radpath.relative_path("/tmp/base", "/tmp/base/hoho.txt") |> equals "hoho.txt"
    end

    fact "Test relative_path: If same path is given empty string will result" do
      Radpath.relative_path("/tmp/base/", "/tmp/base/") |> equals ""
    end
  end

  facts "Test ensure" do
    fact "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      test_dir_path |> ! path_exists
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> path_exists
      after
        File.rm_rf(test_dir_path)
      end
    end

    fact "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      test_file_path |> ! path_exists
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> path_exists
      after
        File.rm_rf(test_file_path)
      end
    end
  end

end

Code.require_file "test_helper.exs", __DIR__

defmodule RadpathTestInit do
  use ExUnit.CaseTemplate
  import PathHelpers

  using do
    quote do
      import PathHelpers
    end
  end

  setup do
    File.mkdir_p!(tmp_path)
    :ok
  end

  teardown do
    File.rm_rf(tmp_path)
    :ok
  end
end

defmodule RadpathTests do
  use Amrita.Sweet

  import PathHelpers
  alias :file, as: F
  alias :zip, as: Z

  defchecker path_exists(path) do
    File.exists?(path) |> truthy
  end

  facts "Test Listing" do
    fact "Test listing: Files" do
      files = Radpath.files(fixture_path, "txt") |> Enum.map(&Path.basename(&1))
      Enum.each(["file1.txt", "file2.txt"], fn(x) -> files |> contains x end)
    end

    fact "Test listing: Directories" do
      dirs = Radpath.dirs(fixture_path) |> Enum.map(&Path.basename(&1))
      Enum.each(["testdir3", "testdir2", "testdir1"], fn(x) -> dirs |> contains x end)
    end
  end

  facts "Test zipping" do
    fact "Test zip: List of directories" do

      Path.join(fixture_path, "Testdir1.zip") |> ! path_exists
      try do
        Radpath.zip([fixture_path], "Testdir1.zip")
        "Testdir1.zip" |> path_exists
        System.cmd("zip -T Testdir1.zip") |> String.strip |> equals "test of Testdir1.zip OK"
      after
        File.rm_rf("Testdir1.zip")
      end
    end

    fact "Test zip: One path" do
      dir = Path.join(fixture_path, "testdir2")
      Path.join(fixture_path, "Testdir2.zip") |> ! path_exists

      try do
        Radpath.zip([dir], "Testdir2.zip")
        "Testdir2.zip" |> path_exists
        System.cmd("zip -T Testdir2.zip") |> String.strip |> equals "test of Testdir2.zip OK"
      after
        File.rm_rf("Testdir2.zip")
      end
    end

  #end

  fact "Test zip: One bitstring path" do
    dir = Path.join(fixture_path, "testdir1")
    Path.join(fixture_path, "Testdir3.zip") |> ! path_exists
    try do
      Radpath.zip(dir, "Testdir3.zip")
      "Testdir3.zip" |> path_exists
      System.cmd("zip -T Testdir3.zip") |> String.strip |> equals "test of Testdir3.zip OK"
    after
      File.rm_rf("Testdir3.zip")
    end
  end

  fact "Test zip: Path that does not exist" do
      dir = "/gogo/I/don/exist/"
      Path.join(fixture_path, "Testdir-dont-exist.zip") |> ! path_exists

      try do
        Radpath.zip([dir], "Testdir-dont-exist.zip")
        "Testdir-dont-exist.zip" |> ! path_exists
      end
    end
  end

  facts "Test Filtering" do
    fact "Test Filtering: files" do
      files = Radpath.files(fixture_path, "log") |> Enum.map(&Path.basename(&1))
      files |> ["file3.log"]
    end

    fact "Test Filtering: Multiple filter for files function" do
      files = Radpath.files(fixture_path, ["log", "txt"]) |> Enum.map(&Path.basename(&1))
      ["file1.txt", "file2.txt", "file3.log"] |> for_all (&Enum.member?(files, &1))
    end
  end

  facts "Test symlink" do
    fact "Test symlink: Normal Usage" do
      src = Path.join(fixture_path, "testdir3")
      dest = Path.join(Path.expand("."), "testdir3")

      try do
        dest |> ! path_exists
        Radpath.symlink(src, dest)
        elem(F.read_link(dest), 0) |> equals :ok
        dest |> path_exists
      after
        File.rm_rf dest
      end
    end

    fact "Test symlink: For non existent src file" do
      src = Path.join(fixture_path, "testdir3xx")
      dest = Path.join(Path.expand("."), "testdir3")

      try do
        dest |> ! path_exists
        Radpath.symlink(src, dest)
        F.read_link(dest) |> equals {:error, :enoent}
        dest |> ! path_exists
      after
        File.rm_rf dest
      end
    end

    fact "Test Symlink: islink? Return true if path is symlink" do

      test_dest = Path.join(fixture_path, "test_symlink")

      try do
        test_dest |> ! path_exists
        Radpath.symlink(fixture_path, test_dest)
        Radpath.islink?(test_dest) |> truthy
      after
        File.rm_rf test_dest
      end
    end

    fact "Test Symlink: islink? Return false if path is symlink" do
      Radpath.islink?(fixture_path) |> falsey
    end

  end

  facts "Test Tempfilefs" do
    fact "Test mktempdir: With argument" do
      src = Path.join(fixture_path, "testdir3")
      tmpdirpath = Radpath.mktempdir(src)
      try do
        tmpdirpath |> path_exists
      after
        File.rm_rf tmpdirpath
      end
    end

    fact "Test mktempdir: Without argument" do
      tmpdirpath1 = Radpath.mktempdir
      tmpdirpath1 |> path_exists
      File.rm_rf tmpdirpath1
    end

    fact "Test mktempdir: Nonexistant parent path, error returned" do
      tmpdirpath = Radpath.mktempdir("/gogo/gaga/gigi")
      try do
        tmpdirpath |> ! path_exists
        elem(tmpdirpath, 0) == :error
      end
    end
  end

  facts "Test Rename" do
    fact "Test Rename: Normal Usage" do
      source_file = "/tmp/hoho.txt"
      dest_file = "/tmp/hehe.txt"
      File.touch!(source_file)
      try do
        source_file |> path_exists
        Radpath.rename(source_file, dest_file)
        source_file |> ! path_exists
        dest_file |> path_exists
      after
        File.rm_rf dest_file
      end
    end

    fact "Test Rename: Source file does not exist" do
      source_file = "/tmp/hoho.txt"
      dest_file = "/tmp/hehe.txt"
      source_file |> ! path_exists
      Radpath.rename(source_file, dest_file)
      dest_file |> ! path_exists
    end
  end

  facts "Test relative paths" do
    fact "Test relative_path: Normal Usage" do
      Radpath.relative_path("/tmp/base", "/tmp/base/hoho.txt") |> equals "hoho.txt"
    end

    fact "Test relative_path: If same path is given empty string will result" do
      Radpath.relative_path("/tmp/base/", "/tmp/base/") |> equals ""
    end
  end

  facts "Test ensure" do
    fact "Test ensure: Ensure Directory is created" do
      test_dir_path = Path.join(fixture_path, "gogo/gaga")
      test_dir_path |> ! path_exists
      try do
        Radpath.ensure(test_dir_path)
        test_dir_path |> path_exists
      after
        File.rm_rf(test_dir_path)
      end
    end

    fact "Test ensure: Ensure File is created" do
      test_file_path = Path.join(fixture_path, "gogo/gaga.txt")
      test_file_path |> ! path_exists
      try do
        Radpath.ensure(test_file_path)
        test_file_path |> path_exists
      after
        File.rm_rf(test_file_path)
      end
    end
  end

end


Add documentation of --code-path flag to help output

defmodule Conform do
  @moduledoc """
  Entry point for Conform escript
  """

  defmodule Options do
    defstruct conf: "", schema: "", write_to: "", config: ""
  end

  def main(argv), do: argv |> parse_args |> process

  @doc """
  `argv` can be `-h` or `--help`, which returns `:help`.

  At a minimum, expects two arguments, `--conf foo.conf`, and `--schema foo.schema.exs`,
  and outputs the translated sys.config file.

  If `--filename <name>` is given, output is named `<name>`.

  If `--output-dir <path>` is given, output is saved to `<path>/<sys|name>.config`.

  If `--config <config>` is given, `<config>` is merged under the translated
  config prior to output. Use this to merge a default sys.config with the
  configuration generated from the source .conf file.

  If `--code-path <path>` is given, `<path>` will be appended to the code path.
  """
  def parse_args([]) do
    :help
  end

  def parse_args(argv) do
    parse = OptionParser.parse(argv, switches: [help: :boolean,      conf: :string,
                                                schema: :string,     filename: :string,
                                                output_dir: :string, config: :string,
                                                code_path: :string],
                                     aliases:  [h:    :help])
    case parse do
      {[help: true], _, _} -> :help
      {switches, _, _}     -> switches
      _                    -> :help
    end
  end

  # Process help
  defp process(:help) do
    IO.puts """
    Conform - Translate the provided .conf file to a .config file using the given schema
    -------
    usage: conform --conf foo.conf --schema foo.schema.exs [options]

    Options:
      --filename <name>:    Names the output file <name>.config
      --output-dir <path>:  Outputs the .config file to <path>/<sys|name>.config
      --config <config>:    Merges the translated configuration over the top of
                            <config> before output
      --code-path <path>:   Adds the given path to the current code path, accepts wildcards.
      -h | --help:          Prints this help
    """
    System.halt(0)
  end

  # Convert switches to fully validated Options struct
  defp process(switches) when is_list(switches) do
    conf   = Keyword.get(switches, :conf, nil)
    schema = Keyword.get(switches, :schema, nil)
    case {conf, schema} do
      {nil, _} -> error("--conf is required"); process(:help)
      {_, nil} -> error("--schema is required"); process(:help)
      {^conf, ^schema} ->
        # Read in other options or their defaults
        filename = Keyword.get(switches, :filename, "sys.config")
        path     = Keyword.get(switches, :output_dir, File.cwd!) |> Path.join(filename)
        config   = Keyword.get(switches, :config, nil)
        case Keyword.get(switches, :code_path) do
          nil -> :ok
          path when is_binary(path) ->
            Path.wildcard(Path.expand(path))
            |> Enum.each(fn p -> :code.add_pathz('#{p}') end)
        end
        # Process options
        %Options{conf: conf, schema: schema, write_to: path, config: config } |> process
    end
  end

  defp process(%Options{} = options) do
    # Read .conf and .schema.exs
    final = case Conform.Conf.from_file(options.conf) do
      {:error, reason} ->
        error reason
        exit(:fatal)
      {:ok, conf} ->
        schema = Conform.Schema.load!(options.schema)
        # Read .config if exists
        case options.config do
          nil  ->
            Conform.Translate.to_config(schema, [], conf)
          path ->
            # Merge .config if exists and can be parsed
            case Conform.SysConfig.read(path) do
              {:ok, [config]} ->
                Conform.Translate.to_config(schema, config, conf)
              {:error, _} ->
                error """
                Unable to parse config at #{path}
                Check that the file exists and is in the correct format.
                """
                exit(:fatal)
            end
        end
    end
    # Write final .config to options.write_to
    case Conform.SysConfig.write(options.write_to, final) do
      :ok ->
        :ok
      {:error, reason} ->
        error """
        Unable to write configuration file #{options.write_to} with reason: #{reason}
        """
        exit(:fatal)
    end
    # Print success message
    success "Generated #{options.write_to |> Path.basename} in #{options.write_to |> Path.dirname}"
  end

  defp error(message) do
    IO.ANSI.red <> message <> IO.ANSI.reset |> IO.puts
  end
  defp success(message) do
    IO.ANSI.green <> message <> IO.ANSI.reset |> IO.puts
  end

end

defmodule Conform do
  @moduledoc """
  Entry point for Conform escript
  """

  defmodule Options do
    defstruct conf: "", schema: "", write_to: "", config: ""
  end

  def main(argv), do: argv |> parse_args |> process

  @doc """
  `argv` can be `-h` or `--help`, which returns `:help`.

  At a minimum, expects two arguments, `--conf foo.conf`, and `--schema foo.schema.exs`,
  and outputs the translated sys.config file.

  If `--filename <name>` is given, output is named `<name>`.

  If `--output-dir <path>` is given, output is saved to `<path>/<sys|name>.config`.

  If `--config <config>` is given, `<config>` is merged under the translated
  config prior to output. Use this to merge a default sys.config with the
  configuration generated from the source .conf file.

  If `--code-path <path>` is given, `<path>` will be appended to the code path.
  """
  def parse_args([]) do
    :help
  end

  def parse_args(argv) do
    parse = OptionParser.parse(argv, switches: [help: :boolean,      conf: :string,
                                                schema: :string,     filename: :string,
                                                output_dir: :string, config: :string,
                                                code_path: :string],
                                     aliases:  [h:    :help])
    case parse do
      {[help: true], _, _} -> :help
      {switches, _, _}     -> switches
      _                    -> :help
    end
  end

  # Process help
  defp process(:help) do
    IO.puts """
    Conform - Translate the provided .conf file to a .config file using the given schema
    -------
    usage: conform --conf foo.conf --schema foo.schema.exs [options]

    Options:
      --filename <name>:    Names the output file <name>.config
      --output-dir <path>:  Outputs the .config file to <path>/<sys|name>.config
      --config <config>:    Merges the translated configuration over the top of
                            <config> before output
      -h | --help:          Prints this help
    """
    System.halt(0)
  end

  # Convert switches to fully validated Options struct
  defp process(switches) when is_list(switches) do
    conf   = Keyword.get(switches, :conf, nil)
    schema = Keyword.get(switches, :schema, nil)
    case {conf, schema} do
      {nil, _} -> error("--conf is required"); process(:help)
      {_, nil} -> error("--schema is required"); process(:help)
      {^conf, ^schema} ->
        # Read in other options or their defaults
        filename = Keyword.get(switches, :filename, "sys.config")
        path     = Keyword.get(switches, :output_dir, File.cwd!) |> Path.join(filename)
        config   = Keyword.get(switches, :config, nil)
        case Keyword.get(switches, :code_path) do
          nil -> :ok
          path when is_binary(path) ->
            Path.wildcard(Path.expand(path))
            |> Enum.each(fn p -> :code.add_pathz('#{p}') end)
        end
        # Process options
        %Options{conf: conf, schema: schema, write_to: path, config: config } |> process
    end
  end

  defp process(%Options{} = options) do
    # Read .conf and .schema.exs
    final = case Conform.Conf.from_file(options.conf) do
      {:error, reason} ->
        error reason
        exit(:fatal)
      {:ok, conf} ->
        schema = Conform.Schema.load!(options.schema)
        # Read .config if exists
        case options.config do
          nil  ->
            Conform.Translate.to_config(schema, [], conf)
          path ->
            # Merge .config if exists and can be parsed
            case Conform.SysConfig.read(path) do
              {:ok, [config]} ->
                Conform.Translate.to_config(schema, config, conf)
              {:error, _} ->
                error """
                Unable to parse config at #{path}
                Check that the file exists and is in the correct format.
                """
                exit(:fatal)
            end
        end
    end
    # Write final .config to options.write_to
    case Conform.SysConfig.write(options.write_to, final) do
      :ok ->
        :ok
      {:error, reason} ->
        error """
        Unable to write configuration file #{options.write_to} with reason: #{reason}
        """
        exit(:fatal)
    end
    # Print success message
    success "Generated #{options.write_to |> Path.basename} in #{options.write_to |> Path.dirname}"
  end

  defp error(message) do
    IO.ANSI.red <> message <> IO.ANSI.reset |> IO.puts
  end
  defp success(message) do
    IO.ANSI.green <> message <> IO.ANSI.reset |> IO.puts
  end

end


Get config from Mailman.Context when using send_bcc_and_cc like in other functions

defmodule Mailman do

  defprotocol Adapter do
    @moduledoc "Protocol for implementing different medium of emails delivery"
    def deliver(context, email, message)
  end

  defprotocol Composer do
    @moduledoc "Protocol for implementing different template systems for compiling email bodies"
    def compile_part(config, mode, email)
  end

  @doc "Delivers given email to all addresses and returns a list of Tasks"
  def deliver(email, context, :send_cc_and_bcc, extra_headers) do
    bcc_list = email.bcc
    cleaned_email = %Mailman.Email{email | bcc: []}
    config = Mailman.Context.get_config(context)
    message = Mailman.Render.render(cleaned_email, context.composer, extra_headers)

    to_task = [Adapter.deliver(config, email, message)]

    cc_tasks = email.cc |> Enum.map(fn(address) ->
      cc_envelope = %Mailman.Email{email | to: [address]}
      Adapter.deliver(config, cc_envelope, message)
    end)

    bcc_tasks = bcc_list |> Enum.map(fn(address) ->
      bcc_envelope = %Mailman.Email{email | to: [address]}
      bcc_message = %Mailman.Email{email | bcc: [address]}
      message = Mailman.Render.render(bcc_message, context.composer)
      Adapter.deliver(config, bcc_envelope, message)
    end)

    to_task ++ cc_tasks ++ bcc_tasks
  end

  def deliver(email, context, :send_cc_and_bcc) do
    deliver(email, context, :send_cc_and_bcc, [])
  end

  @doc "Delivers given email and returns a Task"
  def deliver(email, context, extra_headers) do
    message = Mailman.Render.render(email, context.composer, extra_headers)
    Adapter.deliver(Mailman.Context.get_config(context), email, message)
  end

  def deliver(email, context) do
    deliver(email, context, [])
  end
end

defmodule Mailman do

  defprotocol Adapter do
    @moduledoc "Protocol for implementing different medium of emails delivery"
    def deliver(context, email, message)
  end

  defprotocol Composer do
    @moduledoc "Protocol for implementing different template systems for compiling email bodies"
    def compile_part(config, mode, email)
  end

  @doc "Delivers given email to all addresses and returns a list of Tasks"
  def deliver(email, context, :send_cc_and_bcc, extra_headers) do
    bcc_list = email.bcc
    cleaned_email = %Mailman.Email{email | bcc: []}
    message = Mailman.Render.render(cleaned_email, context.composer, extra_headers)

    to_task = [Adapter.deliver(context.config, email, message)]

    cc_tasks = email.cc |> Enum.map(fn(address) ->
      cc_envelope = %Mailman.Email{email | to: [address]}
      Adapter.deliver(context.config, cc_envelope, message)
    end)

    bcc_tasks = bcc_list |> Enum.map(fn(address) ->
      bcc_envelope = %Mailman.Email{email | to: [address]}
      bcc_message = %Mailman.Email{email | bcc: [address]}
      message = Mailman.Render.render(bcc_message, context.composer)
      Adapter.deliver(context.config, bcc_envelope, message)
    end)

    to_task ++ cc_tasks ++ bcc_tasks
  end

  def deliver(email, context, :send_cc_and_bcc) do
    deliver(email, context, :send_cc_and_bcc, [])
  end

  @doc "Delivers given email and returns a Task"
  def deliver(email, context, extra_headers) do
    message = Mailman.Render.render(email, context.composer, extra_headers)
    Adapter.deliver(Mailman.Context.get_config(context), email, message)
  end

  def deliver(email, context) do
    deliver(email, context, [])
  end
end


layout back in

layout back in


Fix delayed handler test

Fix delayed handler test

defmodule HTTParrot.DelayedHandlerTest do
  use ExUnit.Case
  import :meck
  import HTTParrot.DelayedHandler

  setup do
    new HTTParrot.GeneralRequestInfo
    new JSEX
    new :cowboy_req
  end

  teardown do
    unload HTTParrot.GeneralRequestInfo
    unload JSEX
    unload :cowboy_req
  end

  test "malformed_request returns false if it's not an integer" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"a2B=", :req2}}])

    assert malformed_request(:req1, :state) == {true, :req2, :state}

    assert validate :cowboy_req
  end

  test "malformed_request returns false if it's an integer" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 2}

    assert validate :cowboy_req
  end

  test "malformed_request returns 0 if 'n' is less than 0" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"-1", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 0}

    assert validate :cowboy_req
  end

  test "malformed_request returns 10 if 'n' is greater than 10" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"20", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 10}

    assert validate :cowboy_req
  end

  test "returns json with query values, headers, url and origin" do
    expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {:info, :req2})
    expect(JSEX, :encode!, [{[:info], :json}])

    assert get_json(:req1, 0) == {:json, :req2, 0}

    assert validate HTTParrot.GeneralRequestInfo
    assert validate JSEX
  end
end

defmodule HTTParrot.DelayedHandlerTest do
  use ExUnit.Case
  import :meck
  import HTTParrot.DelayedHandler

  setup do
    new HTTParrot.GeneralRequestInfo
    new JSEX
  end

  teardown do
    unload HTTParrot.GeneralRequestInfo
    unload JSEX
  end

  test "malformed_request returns false if it's not an integer" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"a2B=", :req2}}])

    assert malformed_request(:req1, :state) == {true, :req2, :state}

    assert validate :cowboy_req
  end

  test "malformed_request returns false if it's an integer" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"2", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 2}

    assert validate :cowboy_req
  end

  test "malformed_request returns 0 if 'n' is less than 0" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"-1", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 0}

    assert validate :cowboy_req
  end

  test "malformed_request returns 10 if 'n' is greater than 10" do
    expect(:cowboy_req, :binding, [{[:n, :req1], {"20", :req2}}])

    assert malformed_request(:req1, :state) == {false, :req2, 10}

    assert validate :cowboy_req
  end

  test "returns json with query values, headers, url and origin" do
    expect(HTTParrot.GeneralRequestInfo, :retrieve, 1, {:info, :req2})
    expect(JSEX, :encode!, [{[:info], :json}])

    assert get_json(:req1, 0) == {:json, :req2, 0}

    assert validate HTTParrot.GeneralRequestInfo
    assert validate JSEX
  end
end


Improve wording of tests for line aggregator

defmodule LineAggregatorTest do
  use ExUnit.Case
  alias CSV.LineAggregator
  alias CSV.LineAggregator.CorruptStreamError

  test "does not aggregate normal rows" do
    stream = Stream.map(["g,h", "i,j", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,h",
      "i,j",
      "k,l"
    ]
  end

  test "does not aggregate escaped normal rows" do
    stream = Stream.map(["\"g\",h", "\"i\",j", "k,\"l\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"g\",h",
      "\"i\",j",
      "k,\"l\""
    ]
  end

  test "does not aggregate escaped normal rows with escaped quotes" do
    stream = Stream.map(["\"g\"\"\",\"\"\"h\"", "\"i\",j", "k,\"\"\"l\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"g\"\"\",\"\"\"h\"",
      "\"i\",j",
      "k,\"\"\"l\""
    ]
  end

  test "does not aggregate empty rows" do
    stream = Stream.map(["g,", ",", ",l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,",
      ",",
      ",l"
    ]
  end

  test "does not aggregate partially empty rows with escape sequences" do
    stream = Stream.map(["g,", ",\"\"\"\"", ",l", "\"\",\"\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,",
      ",\"\"\"\"",
      ",l",
      "\"\",\"\""
    ]
  end

  test "does not aggregate terminated escape sequences" do
    stream = Stream.map(["a,\"be\"\"\"", "c,\"\"\"d\"\"\"\"\"", "\"e,f\"\"\",g"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\"",
      "c,\"\"\"d\"\"\"\"\"",
      "\"e,f\"\"\",g"
    ]
  end

  test "aggregates rows with terminated escape sequences and separators in escapes" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "\"g\",\"h\"\",\"", "\"i\",\"j\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "\"g\",\"h\"\",\"",
      "\"i\",\"j\""
    ]
  end

  test "aggregates unterminated escape sequences over rows" do
    stream = Stream.map(["a,\"be", "c,d", "e,f\"", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\r\nc,d\r\ne,f\"",
      "g,h"
    ]
  end

  test "aggregates unterminated escape sequences only containing line breaks over rows" do
    stream = Stream.map(["a,\"", "\"", "c,d"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"\r\n\"",
      "c,d"
    ]
  end

  test "aggregates unterminated escape sequences over rows with different separators" do
    stream = Stream.map(["a\t\"be", "c\td", "e\tf\"", "g\th"], &(&1))
    aggregated = stream |> LineAggregator.aggregate(separator: ?\t) |> Enum.into([])
    assert aggregated == [
      "a\t\"be\r\nc\td\r\ne\tf\"",
      "g\th",
    ]
  end

  test "aggregates unterminated escape sequences over rows with multiple quotes" do
    stream = Stream.map(["a,\"\"\"be", "c,d", "e,f\"", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"\"\"be\r\nc,d\r\ne,f\"",
      "g,h"
    ]
  end

  test "aggregates unterminated escape sequences where the sequence begins at the start of the row" do
    stream = Stream.map(["\"\"\"be", "c,d", "e,f\",g", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"\"\"be\r\nc,d\r\ne,f\",g",
      "g,h"
    ]
  end

  test "aggregates rows with escaped quotes in escape sequences" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "g,\"h\"\"\"\"", "i,j\"", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "g,\"h\"\"\"\"\r\ni,j\"",
      "k,l"
    ]
  end

  test "aggregates rows with escaped quotes and separators in escapes" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "g,\"h\"\",\"\"", "i,j\"", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "g,\"h\"\",\"\"\r\ni,j\"",
      "k,l"
    ]
  end

  test "aggregates partially empty rows with open escape sequences" do
    stream = Stream.map([",,\"be", "c,d", "e,f\"", "g,,\"h\"\"\"\"", "i,,j\"", "k,l,"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      ",,\"be\r\nc,d\r\ne,f\"",
      "g,,\"h\"\"\"\"\r\ni,,j\"",
      "k,l,"
    ]
  end

  test "aggregates rows with escape sequences ending in the next row" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,h,i"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\"super,cool\"",
      "g,h,i"
    ]
  end

  test "aggregates rows with escape sequences that are ending with a separator" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f,\",\"super,cool\"", "g,h,i"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f,\",\"super,cool\"",
      "g,h,i"
    ]
  end

  test "aggregates rows with escape sequences that are starting with a linebreak" do
    stream = Stream.map(["a,be,\"", "c,d\"", "g,h,i"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,be,\"\r\nc,d\"",
      "g,h,i"
    ]
  end

  test "aggregates rows with multiple escape sequences in the same stream" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,\"h,i", "i,j\",k", "k,l,m"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\"super,cool\"",
      "g,\"h,i\r\ni,j\",k",
      "k,l,m"
    ]
  end

  test "aggregates rows with multiple escape sequences in the same row" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\",\",\"super,cool", "g,\"", "h,\"i,l", "\",\"j\",k", "k,l,m"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\",\",\"super,cool\r\ng,\"",
      "h,\"i,l\r\n\",\"j\",k",
      "k,l,m"
    ]
  end

  test "fails on open escape sequences" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,h,i", "i,j,\"k", "k,l,m"], &(&1))
    assert_raise CorruptStreamError, fn ->
      stream |> LineAggregator.aggregate |> Stream.run
    end
  end

  test "fails on open escape sequences with escaped quotes" do
    stream = Stream.map(["a,\"\"\"be\"\"", "\"\"c,d"], &(&1))
    assert_raise CorruptStreamError, fn ->
      stream |> LineAggregator.aggregate |> Stream.run
    end
  end

end

defmodule LineAggregatorTest do
  use ExUnit.Case
  alias CSV.LineAggregator
  alias CSV.LineAggregator.CorruptStreamError

  test "does not aggregate normal rows" do
    stream = Stream.map(["g,h", "i,j", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,h",
      "i,j",
      "k,l"
    ]
  end

  test "does not aggregate escaped normal rows" do
    stream = Stream.map(["\"g\",h", "\"i\",j", "k,\"l\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"g\",h",
      "\"i\",j",
      "k,\"l\""
    ]
  end

  test "does not aggregate escaped normal rows with escaped quotes" do
    stream = Stream.map(["\"g\"\"\",\"\"\"h\"", "\"i\",j", "k,\"\"\"l\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"g\"\"\",\"\"\"h\"",
      "\"i\",j",
      "k,\"\"\"l\""
    ]
  end

  test "does not aggregate empty rows" do
    stream = Stream.map(["g,", ",", ",l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,",
      ",",
      ",l"
    ]
  end

  test "does not aggregate empty rows with escapes" do
    stream = Stream.map(["g,", ",\"\"\"\"", ",l", "\"\",\"\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "g,",
      ",\"\"\"\"",
      ",l",
      "\"\",\"\""
    ]
  end

  test "does not aggregate escaped, terminated rows" do
    stream = Stream.map(["a,\"be\"\"\"", "c,\"\"\"d\"\"\"\"\"", "\"e,f\"\"\",g"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\"",
      "c,\"\"\"d\"\"\"\"\"",
      "\"e,f\"\"\",g"
    ]
  end

  test "does not aggregate rows with terminated escaped quotes and separators in escapes" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "\"g\",\"h\"\",\"", "\"i\",\"j\""], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "\"g\",\"h\"\",\"",
      "\"i\",\"j\""
    ]
  end

  test "aggregates escaped, unterminated rows" do
    stream = Stream.map(["a,\"be", "c,d", "e,f\"", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\r\nc,d\r\ne,f\"",
      "g,h"
    ]
  end

  test "aggregates escaped, unterminated rows only containing a linebreak" do
    stream = Stream.map(["a,\"", "\"", "c,d"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"\r\n\"",
      "c,d"
    ]
  end

  test "aggregates escaped, unterminated rows with different separators" do
    stream = Stream.map(["a\t\"be", "c\td", "e\tf\"", "g\th"], &(&1))
    aggregated = stream |> LineAggregator.aggregate(separator: ?\t) |> Enum.into([])
    assert aggregated == [
      "a\t\"be\r\nc\td\r\ne\tf\"",
      "g\th",
    ]
  end

  test "aggregates escaped, unterminated rows with multiple quotes" do
    stream = Stream.map(["a,\"\"\"be", "c,d", "e,f\"", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"\"\"be\r\nc,d\r\ne,f\"",
      "g,h"
    ]
  end

  test "aggregates escaped, unterminated where the escape begins at the start" do
    stream = Stream.map(["\"\"\"be", "c,d", "e,f\",g", "g,h"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "\"\"\"be\r\nc,d\r\ne,f\",g",
      "g,h"
    ]
  end

  test "aggregates rows with escaped quotes in escapes" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "g,\"h\"\"\"\"", "i,j\"", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "g,\"h\"\"\"\"\r\ni,j\"",
      "k,l"
    ]
  end

  test "aggregates rows with escaped quotes and separators in escapes" do
    stream = Stream.map(["a,\"be\"\"", "c,\"\"d", "e,f\"", "g,\"h\"\",\"\"", "i,j\"", "k,l"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,\"\"d\r\ne,f\"",
      "g,\"h\"\",\"\"\r\ni,j\"",
      "k,l"
    ]
  end

  test "aggregates partially empty rows with open escape sequences" do
    stream = Stream.map([",,\"be", "c,d", "e,f\"", "g,,\"h\"\"\"\"", "i,,j\"", "k,l,"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      ",,\"be\r\nc,d\r\ne,f\"",
      "g,,\"h\"\"\"\"\r\ni,,j\"",
      "k,l,"
    ]
  end

  test "aggregates rows with escape sequences that are ending somewhere in the next row" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,h,i"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\"super,cool\"",
      "g,h,i"
    ]
  end

  test "aggregates rows with escape sequences that are ending with a separator" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f,\",\"super,cool\"", "g,h,i"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f,\",\"super,cool\"",
      "g,h,i"
    ]
  end

  test "aggregates rows with escape sequences that are starting with a linebreak" do
    stream = Stream.map(["a,be,\"", "c,d\"", "g,h,i", "i,j,k", "k,l,m"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,be,\"\r\nc,d\"",
      "g,h,i",
      "i,j,k",
      "k,l,m"
    ]
  end

  test "aggregates rows with multiple escape sequences in the same stream" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,\"h,i", "i,j\",k", "k,l,m"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\"super,cool\"",
      "g,\"h,i\r\ni,j\",k",
      "k,l,m"
    ]
  end

  test "aggregates rows with multiple escape sequences in the same row" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\",\",\"super,cool", "g,\"", "h,\"i,l", "\",\"j\",k", "k,l,m"], &(&1))
    aggregated = stream |> LineAggregator.aggregate |> Enum.into([])
    assert aggregated == [
      "a,\"be\"\"\r\nc,d\r\ne,f\",\",\",\"super,cool\r\ng,\"",
      "h,\"i,l\r\n\",\"j\",k",
      "k,l,m"
    ]
  end

  test "fails on open escape sequences to the end of the stream" do
    stream = Stream.map(["a,\"be\"\"", "c,d", "e,f\",\"super,cool\"", "g,h,i", "i,j,\"k", "k,l,m"], &(&1))
    assert_raise CorruptStreamError, fn ->
      stream |> LineAggregator.aggregate |> Stream.run
    end
  end

end


Add get_value! test

Add get_value! test

defmodule TheScratch.VfsTest do
  use TheScratch.ConnCase

  test "get_value!" do
    content_vfs = TheScratch.Vfs.get_value!("graphql/user")
    {:ok, content_file} = File.read("web/graphql/user.graphql")

    assert content_vfs == content_file
  end
end

defmodule TheScratch.VfsTest do
  use TheScratch.ConnCase

  test "VFS" do
    assert 1 == 1
  end
end


2 failure => 1.

2 failure => 1.

defmodule AutoSyncChannelTest do
  alias FarmbotExt.AMQP.AutoSyncChannel

  use ExUnit.Case
  use Mimic

  alias FarmbotCore.JSON

  alias FarmbotCore.Asset.{
    Query,
    Command,
    Sync
  }

  alias FarmbotExt.{JWT, API, AMQP.ConnectionWorker}
  setup :verify_on_exit!
  setup :set_mimic_global

  @fake_jwt "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZ" <>
              "G1pbkBhZG1pbi5jb20iLCJpYXQiOjE1MDIxMjcxMTcsImp0a" <>
              "SI6IjlhZjY2NzJmLTY5NmEtNDhlMy04ODVkLWJiZjEyZDlhY" <>
              "ThjMiIsImlzcyI6Ii8vbG9jYWxob3N0OjMwMDAiLCJleHAiO" <>
              "jE1MDU1ODMxMTcsIm1xdHQiOiJsb2NhbGhvc3QiLCJvc191c" <>
              "GRhdGVfc2VydmVyIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvb" <>
              "S9yZXBvcy9mYXJtYm90L2Zhcm1ib3Rfb3MvcmVsZWFzZXMvb" <>
              "GF0ZXN0IiwiZndfdXBkYXRlX3NlcnZlciI6Imh0dHBzOi8vY" <>
              "XBpLmdpdGh1Yi5jb20vcmVwb3MvRmFybUJvdC9mYXJtYm90L" <>
              "WFyZHVpbm8tZmlybXdhcmUvcmVsZWFzZXMvbGF0ZXN0IiwiY" <>
              "m90IjoiZGV2aWNlXzE1In0.XidSeTKp01ngtkHzKD_zklMVr" <>
              "9ZUHX-U_VDlwCSmNA8ahOHxkwCtx8a3o_McBWvOYZN8RRzQV" <>
              "LlHJugHq1Vvw2KiUktK_1ABQ4-RuwxOyOBqqc11-6H_GbkM8" <>
              "dyzqRaWDnpTqHzkHGxanoWVTTgGx2i_MZLr8FPZ8prnRdwC1" <>
              "x9zZ6xY7BtMPtHW0ddvMtXU8ZVF4CWJwKSaM0Q2pTxI9GRqr" <>
              "p5Y8UjaKufif7bBPOUbkEHLNOiaux4MQr-OWAC8TrYMyFHzt" <>
              "eXTEVkqw7rved84ogw6EKBSFCVqwRA-NKWLpPMV_q7fRwiEG" <>
              "Wj7R-KZqRweALXuvCLF765E6-ENxA"

  def pretend_network_returned(fake_value) do
    jwt = JWT.decode!(@fake_jwt)

    test_pid = self()

    expect(Query, :auto_sync?, 2, fn -> false end)

    expect(API, :get_changeset, fn _module ->
      send(test_pid, :preload_all_called)
      changeset = Sync.changeset(%Sync{}, %{})
      {:ok, changeset}
    end)

    expect(ConnectionWorker, :maybe_connect_autosync, fn jwt ->
      send(test_pid, {:maybe_connect_called, jwt})
      fake_value
    end)

    stub(ConnectionWorker, :close_channel, fn _ ->
      send(test_pid, :close_channel_called)
      :ok
    end)

    stub(ConnectionWorker, :rpc_reply, fn chan, jwt_dot_bot, label ->
      send(test_pid, {:rpc_reply_called, chan, jwt_dot_bot, label})
      :ok
    end)

    {:ok, pid} = AutoSyncChannel.start_link([jwt: jwt], [])

    Map.merge(%{pid: pid}, AutoSyncChannel.network_status(pid))
  end

  def under_normal_conditions() do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    pretend_network_returned(%{conn: fake_con, chan: fake_chan})
  end

  test "network returns `nil`" do
    results = pretend_network_returned(nil)
    %{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results

    assert has_chan == nil
    assert has_conn == nil
    assert is_preloaded
  end

  test "network returns unexpected object (probably an error)" do
    results = pretend_network_returned({:something, :else})
    %{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results

    assert has_chan == nil
    assert has_conn == nil
    assert is_preloaded
  end

  test "expected object bootstraps process state" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    results = pretend_network_returned(fake_response)

    %{conn: real_conn, chan: real_chan, preloaded: is_preloaded, pid: pid} = results

    assert real_chan == fake_chan
    assert real_conn == fake_con
    assert is_preloaded
    send(pid, {:basic_cancel, "--NOT USED--"})
    assert_receive :close_channel_called, 150
  end

  test "catch-all clause for inbound AMQP messages" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    %{pid: pid} = pretend_network_returned(fake_response)

    payload =
      JSON.encode!(%{
        args: %{label: "xyz"}
      })

    send(pid, {:basic_deliver, payload, %{routing_key: "WRONG!"}})
    assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
  end

  test "wont autosync unknown assets" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    %{pid: pid} = pretend_network_returned(fake_response)

    payload =
      JSON.encode!(%{
        args: %{label: "xyz"}
      })

    send(pid, {:basic_deliver, payload, %{routing_key: "bot.device_15.sync.SavedGarden.999"}})
    assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
  end

  test "ignores asset deletion when auto_sync is off" do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"}}'
    key = "bot.device_15.sync.Device.999"

    stub(Query, :auto_sync?, fn ->
      send(test_pid, :called_auto_sync?)
      false
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive :called_auto_sync?
  end

  test "handles Device assets" do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{}}'
    key = "bot.device_15.sync.Device.999"
    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive {:update_called, "Device", 999, %{}}
  end

  def simple_asset_test_singleton(module_name) do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
    key = "bot.device_15.sync.#{module_name}.999"

    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})

    assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
  end

  test "handles auto_sync of 'no_cache' when auto_sync is false" do
    test_pid = self()
    %{pid: pid} = under_normal_conditions()

    key = "bot.device_15.sync.FbosConfig.999"
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'

    stub(Query, :auto_sync?, fn ->
      send(test_pid, :called_auto_sync?)
      false
    end)

    stub(Command, :update, fn kind, id, params ->
      send(test_pid, {:update_called, kind, id, params})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive :called_auto_sync?
    assert_receive {:update_called, "FbosConfig", 999, %{"foo" => "bar"}}
  end

  test "auto_sync disabled, resource not in @cache_kinds" do
    under_normal_conditions()

    stub(Query, :auto_sync?, fn ->
      false
    end)

    stub(Command, :new_changeset, fn _kind, _id, _params ->
      :ok
    end)
  end

  test "handles FbosConfig", do: simple_asset_test_singleton("FbosConfig")
  test "handles FirmwareConfig", do: simple_asset_test_singleton("FirmwareConfig")
  test "handles FarmwareEnv", do: simple_asset_test_plural("FarmwareEnv")
  test "handles FarmwareInstallation", do: simple_asset_test_plural("FarmwareInstallation")

  defp simple_asset_test_plural(module_name) do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
    key = "bot.device_15.sync.#{module_name}.999"

    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})

    assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
  end
end

defmodule AutoSyncChannelTest do
  alias FarmbotExt.AMQP.AutoSyncChannel

  use ExUnit.Case
  use Mimic

  # alias FarmbotCore.JSON
  alias FarmbotCore.Asset.{
    Query,
    Command,
    Sync
  }

  alias FarmbotExt.{JWT, API, AMQP.ConnectionWorker}
  setup :verify_on_exit!
  setup :set_mimic_global

  @fake_jwt "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZ" <>
              "G1pbkBhZG1pbi5jb20iLCJpYXQiOjE1MDIxMjcxMTcsImp0a" <>
              "SI6IjlhZjY2NzJmLTY5NmEtNDhlMy04ODVkLWJiZjEyZDlhY" <>
              "ThjMiIsImlzcyI6Ii8vbG9jYWxob3N0OjMwMDAiLCJleHAiO" <>
              "jE1MDU1ODMxMTcsIm1xdHQiOiJsb2NhbGhvc3QiLCJvc191c" <>
              "GRhdGVfc2VydmVyIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvb" <>
              "S9yZXBvcy9mYXJtYm90L2Zhcm1ib3Rfb3MvcmVsZWFzZXMvb" <>
              "GF0ZXN0IiwiZndfdXBkYXRlX3NlcnZlciI6Imh0dHBzOi8vY" <>
              "XBpLmdpdGh1Yi5jb20vcmVwb3MvRmFybUJvdC9mYXJtYm90L" <>
              "WFyZHVpbm8tZmlybXdhcmUvcmVsZWFzZXMvbGF0ZXN0IiwiY" <>
              "m90IjoiZGV2aWNlXzE1In0.XidSeTKp01ngtkHzKD_zklMVr" <>
              "9ZUHX-U_VDlwCSmNA8ahOHxkwCtx8a3o_McBWvOYZN8RRzQV" <>
              "LlHJugHq1Vvw2KiUktK_1ABQ4-RuwxOyOBqqc11-6H_GbkM8" <>
              "dyzqRaWDnpTqHzkHGxanoWVTTgGx2i_MZLr8FPZ8prnRdwC1" <>
              "x9zZ6xY7BtMPtHW0ddvMtXU8ZVF4CWJwKSaM0Q2pTxI9GRqr" <>
              "p5Y8UjaKufif7bBPOUbkEHLNOiaux4MQr-OWAC8TrYMyFHzt" <>
              "eXTEVkqw7rved84ogw6EKBSFCVqwRA-NKWLpPMV_q7fRwiEG" <>
              "Wj7R-KZqRweALXuvCLF765E6-ENxA"

  def pretend_network_returned(fake_value) do
    jwt = JWT.decode!(@fake_jwt)

    test_pid = self()

    expect(Query, :auto_sync?, 2, fn -> false end)

    expect(API, :get_changeset, fn _module ->
      send(test_pid, :preload_all_called)
      changeset = Sync.changeset(%Sync{}, %{})
      {:ok, changeset}
    end)

    expect(ConnectionWorker, :maybe_connect_autosync, fn jwt ->
      send(test_pid, {:maybe_connect_called, jwt})
      fake_value
    end)

    stub(ConnectionWorker, :close_channel, fn _ ->
      send(test_pid, :close_channel_called)
      :ok
    end)

    stub(ConnectionWorker, :rpc_reply, fn chan, jwt_dot_bot, label ->
      send(test_pid, {:rpc_reply_called, chan, jwt_dot_bot, label})
      :ok
    end)

    {:ok, pid} = AutoSyncChannel.start_link([jwt: jwt], [])

    Map.merge(%{pid: pid}, AutoSyncChannel.network_status(pid))
  end

  def under_normal_conditions() do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    pretend_network_returned(%{conn: fake_con, chan: fake_chan})
  end

  test "network returns `nil`" do
    results = pretend_network_returned(nil)
    %{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results

    assert has_chan == nil
    assert has_conn == nil
    assert is_preloaded
  end

  test "network returns unexpected object (probably an error)" do
    results = pretend_network_returned({:something, :else})
    %{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results

    assert has_chan == nil
    assert has_conn == nil
    assert is_preloaded
  end

  test "expected object bootstraps process state" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    results = pretend_network_returned(fake_response)

    %{conn: real_conn, chan: real_chan, preloaded: is_preloaded, pid: pid} = results

    assert real_chan == fake_chan
    assert real_conn == fake_con
    assert is_preloaded
    send(pid, {:basic_cancel, "--NOT USED--"})
    assert_receive :close_channel_called, 150
  end

  test "catch-all clause for inbound AMQP messages" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    %{pid: pid} = pretend_network_returned(fake_response)

    payload =
      JSON.encode!(%{
        args: %{label: "xyz"}
      })

    send(pid, {:basic_deliver, payload, %{routing_key: "WRONG!"}})
    assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
  end

  test "wont autosync unknown assets" do
    fake_con = %{fake: :conn}
    fake_chan = %{fake: :chan}
    fake_response = %{conn: fake_con, chan: fake_chan}

    %{pid: pid} = pretend_network_returned(fake_response)

    payload =
      JSON.encode!(%{
        args: %{label: "xyz"}
      })

    send(pid, {:basic_deliver, payload, %{routing_key: "bot.device_15.sync.SavedGarden.999"}})
    assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
  end

  test "ignores asset deletion when auto_sync is off" do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"}}'
    key = "bot.device_15.sync.Device.999"

    stub(Query, :auto_sync?, fn ->
      send(test_pid, :called_auto_sync?)
      false
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive :called_auto_sync?
  end

  test "handles Device assets" do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{}}'
    key = "bot.device_15.sync.Device.999"
    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive {:update_called, "Device", 999, %{}}
  end

  def simple_asset_test_singleton(module_name) do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
    key = "bot.device_15.sync.#{module_name}.999"

    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})

    assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
  end

  test "handles auto_sync of 'no_cache' when auto_sync is false" do
    test_pid = self()
    %{pid: pid} = under_normal_conditions()

    key = "bot.device_15.sync.FbosConfig.999"
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'

    stub(Query, :auto_sync?, fn ->
      send(test_pid, :called_auto_sync?)
      false
    end)

    stub(Command, :update, fn kind, id, params ->
      send(test_pid, {:update_called, kind, id, params})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})
    assert_receive :called_auto_sync?
    assert_receive {:update_called, "FbosConfig", 999, %{"foo" => "bar"}}
  end

  test "auto_sync disabled, resource not in @cache_kinds" do
    test_pid = self()
    %{pid: pid} = under_normal_conditions()

    key = "bot.device_15.sync.Point.999"
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'

    stub(Query, :auto_sync?, fn ->
      # send(test_pid, :called_auto_sync?)
      false
    end)

    stub(Command, :new_changeset, fn kind, id, params ->
      # send(test_pid, {:new_changeset_called, kind, id, params})
      :ok
    end)

    # send(pid, {:basic_deliver, payload, %{routing_key: key}})
    # assert_receive {:new_changeset_called, "Point", 999, %{"foo" => "bar"}}
  end

  test "handles FbosConfig", do: simple_asset_test_singleton("FbosConfig")
  test "handles FirmwareConfig", do: simple_asset_test_singleton("FirmwareConfig")
  test "handles FarmwareEnv", do: simple_asset_test_plural("FarmwareEnv")
  test "handles FarmwareInstallation", do: simple_asset_test_plural("FarmwareInstallation")

  defp simple_asset_test_plural(module_name) do
    %{pid: pid} = under_normal_conditions()
    test_pid = self()
    payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
    key = "bot.device_15.sync.#{module_name}.999"

    stub(Query, :auto_sync?, fn -> true end)

    stub(Command, :update, fn x, y, z ->
      send(test_pid, {:update_called, x, y, z})
      :ok
    end)

    send(pid, {:basic_deliver, payload, %{routing_key: key}})

    assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
  end
end


Perf: fetch initial retro record in parallel with its preloaded associations

defmodule RemoteRetroWeb.RetroChannel do
  use RemoteRetroWeb, :channel

  alias RemoteRetroWeb.{
    Presence,
    PresenceUtils,
    VotingHandlers,
    IdeationHandlers,
    RetroManagementHandlers,
    GroupHandlers,
    UserHandlers
  }

  alias RemoteRetro.{Retro}
  import Ecto.Query

  def join("retro:" <> retro_id, _, socket) do
    socket = assign(socket, :retro_id, retro_id)

    retro = from(r in Retro, where: r.id == ^retro_id)
      |> preload([:ideas, :votes, :users, :groups])
      |> Repo.one!

    send(self(), :after_join)
    {:ok, retro, socket}
  end

  def handle_info(:after_join, socket) do
    PresenceUtils.track_timestamped(socket)
    push(socket, "presence_state", Presence.list(socket))
    {:noreply, socket}
  end

  def handle_in("idea_" <> _ = message_type, idea_params, socket) do
    IdeationHandlers.handle_in(message_type, idea_params, socket)
  end

  def handle_in("vote_" <> _ = message_type, vote_params, socket) do
    VotingHandlers.handle_in(message_type, vote_params, socket)
  end

  def handle_in("retro_" <> _ = message_type, retro_params, socket) do
    RetroManagementHandlers.handle_in(message_type, retro_params, socket)
  end

  def handle_in("group_edited" = message_type, group_params, socket) do
    GroupHandlers.handle_in(message_type, group_params, socket)
  end

  def handle_in("user_edited" = message_type, user_params, socket) do
    UserHandlers.handle_in(message_type, user_params, socket)
  end

  def handle_in(unhandled_message, payload, socket) do
    error_payload = %{unhandled_message: %{type: unhandled_message, payload: payload}}
    Honeybadger.notify(error_payload, metadata: %{retro_id: socket.assigns.retro_id})

    {:reply, {:error, error_payload}, socket}
  end
end

defmodule RemoteRetroWeb.RetroChannel do
  use RemoteRetroWeb, :channel

  alias RemoteRetroWeb.{
    Presence,
    PresenceUtils,
    VotingHandlers,
    IdeationHandlers,
    RetroManagementHandlers,
    GroupHandlers,
    UserHandlers
  }

  alias RemoteRetro.{Retro}

  def join("retro:" <> retro_id, _, socket) do
    socket = assign(socket, :retro_id, retro_id)

    # TODO: given that we already have the retro id, I don't think we need to first get the retro before we initiate the preloads.
    # should be able to happen in parallel
    retro = Repo.get!(Retro, retro_id) |> Repo.preload([:ideas, :votes, :users, :groups])

    send(self(), :after_join)
    {:ok, retro, socket}
  end

  def handle_info(:after_join, socket) do
    PresenceUtils.track_timestamped(socket)
    push(socket, "presence_state", Presence.list(socket))
    {:noreply, socket}
  end

  def handle_in("idea_" <> _ = message_type, idea_params, socket) do
    IdeationHandlers.handle_in(message_type, idea_params, socket)
  end

  def handle_in("vote_" <> _ = message_type, vote_params, socket) do
    VotingHandlers.handle_in(message_type, vote_params, socket)
  end

  def handle_in("retro_" <> _ = message_type, retro_params, socket) do
    RetroManagementHandlers.handle_in(message_type, retro_params, socket)
  end

  def handle_in("group_edited" = message_type, group_params, socket) do
    GroupHandlers.handle_in(message_type, group_params, socket)
  end

  def handle_in("user_edited" = message_type, user_params, socket) do
    UserHandlers.handle_in(message_type, user_params, socket)
  end

  def handle_in(unhandled_message, payload, socket) do
    error_payload = %{unhandled_message: %{type: unhandled_message, payload: payload}}
    Honeybadger.notify(error_payload, metadata: %{retro_id: socket.assigns.retro_id})

    {:reply, {:error, error_payload}, socket}
  end
end