Tests for json_encode()

Tests for json_encode()

defmodule FarmbotOS.Lua.Ext.DataManipulationTest do
  use ExUnit.Case
  use Mimic
  setup :verify_on_exit!
  alias FarmbotOS.SysCalls.ResourceUpdate
  alias FarmbotOS.Lua.Ext.DataManipulation

  def lua(test_name, lua_code) do
    FarmbotOS.Lua.eval_assertion(test_name, lua_code)
  end

  test "update_device()" do
    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"name" => "Test Farmbot"} == params
    end)

    lua_code = """
    update_device({name = "Test Farmbot"})
    return true
    """

    assert true == lua("update device test", lua_code)
  end

  test "get_device/0" do
    fake_device = %{fake: :device}
    expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    get_device()
    return true
    """

    assert true == lua("get device test", lua_code)
  end

  test "get_device/1" do
    fake_device = %{name: "my farmbot", id: 23}
    expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    return get_device("id") == 23
    """

    assert true == lua("get device test/1", lua_code)
  end

  test "get_fbos_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_fbos_config(\"id\")"

    assert true == lua("get_fbos_config", lua_code)
  end

  test "get_fbos_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_fbos_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert true == lua("get_fbos_config/1", lua_code)
  end

  test "get_firmware_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_firmware_config(\"id\")"

    assert true == lua("get_firmware_config", lua_code)
  end

  test "get_firmware_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_firmware_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert true == lua("get_firmware_config/1", lua_code)
  end

  test "new_sensor_reading" do
    expect(FarmbotCore.Asset, :new_sensor_reading!, 1, fn params -> params end)

    lua_code = """
    return new_sensor_reading({
      pin = 0,
      mode = 1,
      value = 2,
      x = 3,
      y = 4,
      z = 5,
    })
    """

    assert true == lua("new_sensor_reading/1", lua_code)
  end

  test "take_photo - OK" do
    mock = fn ("take-photo", %{}) -> :ok end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {[], :lua} == actual
  end

  test "take_photo - 'normal' errors" do
    mock = fn ("take-photo", %{}) -> {:error, "whatever"} end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {["whatever"], :lua} == actual
  end

  test "take_photo - malformed errors" do
    mock = fn ("take-photo", %{}) -> {:something_else, "whoops"} end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {[inspect({:something_else, "whoops"})], :lua} == actual
  end

  test "json_decode - OK" do
    actual = DataManipulation.json_decode(["{\"foo\":\"bar\"}"], :lua)
    assert {[[{"foo", "bar"}]], :lua} == actual
  end

  test "json_decode - Error" do
    actual = DataManipulation.json_decode(["no"], :lua)
    assert {[nil, "Error parsing JSON."], :lua} == actual
  end

  test "json_encode - OK" do
    actual = DataManipulation.json_encode([[{"foo", "bar"}]], :lua)
    assert {["{\"foo\":\"bar\"}"], :lua} == actual
  end
end

defmodule FarmbotOS.Lua.Ext.DataManipulationTest do
  use ExUnit.Case
  use Mimic
  setup :verify_on_exit!
  alias FarmbotOS.SysCalls.ResourceUpdate
  alias FarmbotOS.Lua.Ext.DataManipulation

  def lua(test_name, lua_code) do
    FarmbotOS.Lua.eval_assertion(test_name, lua_code)
  end

  test "update_device()" do
    expect(ResourceUpdate, :update_resource, 1, fn "Device", nil, params ->
      assert %{"name" => "Test Farmbot"} == params
    end)

    lua_code = """
    update_device({name = "Test Farmbot"})
    return true
    """

    assert true == lua("update device test", lua_code)
  end

  test "get_device/0" do
    fake_device = %{fake: :device}
    expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    get_device()
    return true
    """

    assert true == lua("get device test", lua_code)
  end

  test "get_device/1" do
    fake_device = %{name: "my farmbot", id: 23}
    expect(FarmbotCore.Asset, :device, 1, fn -> fake_device end)
    expect(FarmbotCore.Asset.Device, :render, 1, fn dev -> dev end)

    lua_code = """
    return get_device("id") == 23
    """

    assert true == lua("get device test/1", lua_code)
  end

  test "get_fbos_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_fbos_config(\"id\")"

    assert true == lua("get_fbos_config", lua_code)
  end

  test "get_fbos_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotCore.Asset, :fbos_config, 1, fn -> fake_config end)
    expect(FarmbotCore.Asset.FbosConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_fbos_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert true == lua("get_fbos_config/1", lua_code)
  end

  test "get_firmware_config/1" do
    fake_config = %{id: 47}
    expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = "return 47 == get_firmware_config(\"id\")"

    assert true == lua("get_firmware_config", lua_code)
  end

  test "get_firmware_config/0" do
    fake_config = %{id: 47, foo: "bar"}
    expect(FarmbotCore.Asset, :firmware_config, 1, fn -> fake_config end)

    expect(FarmbotCore.Asset.FirmwareConfig, :render, 1, fn params -> params end)

    lua_code = """
    c = get_firmware_config()
    return (c.id == 47) and (c.foo == "bar")
    """

    assert true == lua("get_firmware_config/1", lua_code)
  end

  test "new_sensor_reading" do
    expect(FarmbotCore.Asset, :new_sensor_reading!, 1, fn params -> params end)

    lua_code = """
    return new_sensor_reading({
      pin = 0,
      mode = 1,
      value = 2,
      x = 3,
      y = 4,
      z = 5,
    })
    """

    assert true == lua("new_sensor_reading/1", lua_code)
  end

  test "take_photo - OK" do
    mock = fn ("take-photo", %{}) -> :ok end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {[], :lua} == actual
  end

  test "take_photo - 'normal' errors" do
    mock = fn ("take-photo", %{}) -> {:error, "whatever"} end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {["whatever"], :lua} == actual
  end

  test "take_photo - malformed errors" do
    mock = fn ("take-photo", %{}) -> {:something_else, "whoops"} end
    expect(FarmbotOS.SysCalls.Farmware, :execute_script, mock)
    actual = DataManipulation.take_photo(:none, :lua)
    assert {[inspect({:something_else, "whoops"})], :lua} == actual
  end

  test "json_decode - OK" do
    actual = DataManipulation.json_decode(["{\"foo\":\"bar\"}"], :lua)
    assert {[[{"foo", "bar"}]], :lua} == actual
  end

  test "json_decode - Error" do
    actual = DataManipulation.json_decode(["no"], :lua)
    assert {[nil, "Error parsing JSON."], :lua} == actual
  end
end


increases coordinator_controller coverage

defmodule CoursePlanner.CoordinatorControllerTest do
  use CoursePlannerWeb.ConnCase
  alias CoursePlanner.Repo
  alias CoursePlanner.User

  import CoursePlanner.Factory

  setup do
    {:ok, conn: login_as(:coordinator)}
  end

  defp login_as(user_type) do
    user = insert(user_type)

    Phoenix.ConnTest.build_conn()
    |> assign(:current_user, user)
  end

  test "lists all entries on index", %{conn: conn} do
    conn = get conn, coordinator_path(conn, :index)
    assert html_response(conn, 200) =~ "Coordinators"
  end

  test "shows chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator)
    conn = get conn, coordinator_path(conn, :show, coordinator)
    assert html_response(conn, 200) =~ "#{coordinator.name} #{coordinator.family_name}"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, coordinator_path(conn, :show, -1)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator, %{name: "Foo", family_name: "Bar"})
    conn = get conn, coordinator_path(conn, :edit, coordinator)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "does not updates if the resource does not exist", %{conn: conn} do
    conn = put conn, coordinator_path(conn, :update, -1), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 404)
  end

  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    coordinator = insert(:coordinator, %{})
    conn = put conn, coordinator_path(conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert redirected_to(conn) == coordinator_path(conn, :show, coordinator)
    assert Repo.get_by(User, email: "foo@bar.com")
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    coordinator = insert(:coordinator, %{name: "Foo", family_name: "Bar"})
    conn = put conn, coordinator_path(conn, :update, coordinator), %{"user" => %{"email" => "not email"}}
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "deletes chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator)
    conn = delete conn, coordinator_path(conn, :delete, coordinator)
    assert redirected_to(conn) == coordinator_path(conn, :index)
    refute Repo.get(User, coordinator.id)
  end

  test "does not delete chosen resource when does not exist", %{conn: conn} do
    conn = delete conn, coordinator_path(conn, :delete, "-1")
    assert redirected_to(conn) == coordinator_path(conn, :index)
    conn = get conn, coordinator_path(conn, :index)
    assert html_response(conn, 200) =~ "Coordinator was not found."
  end

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, coordinator_path(conn, :new)
    assert html_response(conn, 200) =~ "New coordinator"
  end

  test "does not shows chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = get student_conn, coordinator_path(student_conn, :show, coordinator)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :show, coordinator)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :show, coordinator)
    assert html_response(conn, 403)
  end

  test "does not list entries on index for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, coordinator_path(student_conn, :index)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :index)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :index)
    assert html_response(conn, 403)
  end

  test "does not renders form for editing chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = get student_conn, coordinator_path(student_conn, :edit, coordinator)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :edit, coordinator)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :edit, coordinator)
    assert html_response(conn, 403)
  end

  test "does not delete a chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = delete student_conn, coordinator_path(student_conn, :delete, coordinator.id)
    assert html_response(conn, 403)

    conn = delete teacher_conn, coordinator_path(teacher_conn, :delete, coordinator.id)
    assert html_response(conn, 403)

    conn = delete volunteer_conn, coordinator_path(volunteer_conn, :delete, coordinator.id)
    assert html_response(conn, 403)
  end

  test "does not render form for new coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, coordinator_path(student_conn, :new)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :new)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :new)
    assert html_response(conn, 403)
  end

  test "does not create coordinator for coordinator user when data is invalid", %{conn: conn} do
    conn = post conn, coordinator_path(conn, :create), %{"user" => %{"email" => ""}}
    assert html_response(conn, 200) =~ "Something went wrong."
  end

  test "create coordinator for coordinator user", %{conn: conn} do
    conn = post conn, coordinator_path(conn, :create), %{"user" => %{"email" => "foo@bar.com"}}
    assert redirected_to(conn) == coordinator_path(conn, :index)
    conn = get conn, coordinator_path(conn, :index)
    assert html_response(conn, 200)
  end

  test "does not create coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = post student_conn, coordinator_path(student_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)

    conn = post teacher_conn, coordinator_path(teacher_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)

    conn = post volunteer_conn, coordinator_path(volunteer_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)
  end

  test "does not update chosen coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator, %{})

    conn = put student_conn, coordinator_path(student_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)

    conn = put teacher_conn, coordinator_path(teacher_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)

    conn = put volunteer_conn, coordinator_path(volunteer_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)
  end
end

defmodule CoursePlanner.CoordinatorControllerTest do
  use CoursePlannerWeb.ConnCase
  alias CoursePlanner.Repo
  alias CoursePlanner.User

  import CoursePlanner.Factory

  setup do
    {:ok, conn: login_as(:coordinator)}
  end

  defp login_as(user_type) do
    user = insert(user_type)

    Phoenix.ConnTest.build_conn()
    |> assign(:current_user, user)
  end

  test "lists all entries on index", %{conn: conn} do
    conn = get conn, coordinator_path(conn, :index)
    assert html_response(conn, 200) =~ "Coordinators"
  end

  test "shows chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator)
    conn = get conn, coordinator_path(conn, :show, coordinator)
    assert html_response(conn, 200) =~ "#{coordinator.name} #{coordinator.family_name}"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, coordinator_path(conn, :show, -1)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator, %{name: "Foo", family_name: "Bar"})
    conn = get conn, coordinator_path(conn, :edit, coordinator)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    coordinator = insert(:coordinator, %{})
    conn = put conn, coordinator_path(conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert redirected_to(conn) == coordinator_path(conn, :show, coordinator)
    assert Repo.get_by(User, email: "foo@bar.com")
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    coordinator = insert(:coordinator, %{name: "Foo", family_name: "Bar"})
    conn = put conn, coordinator_path(conn, :update, coordinator), %{"user" => %{"email" => "not email"}}
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "deletes chosen resource", %{conn: conn} do
    coordinator = insert(:coordinator)
    conn = delete conn, coordinator_path(conn, :delete, coordinator)
    assert redirected_to(conn) == coordinator_path(conn, :index)
    refute Repo.get(User, coordinator.id)
  end

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, coordinator_path(conn, :new)
    assert html_response(conn, 200) =~ "New coordinator"
  end

  test "does not shows chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = get student_conn, coordinator_path(student_conn, :show, coordinator)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :show, coordinator)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :show, coordinator)
    assert html_response(conn, 403)
  end

  test "does not list entries on index for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, coordinator_path(student_conn, :index)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :index)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :index)
    assert html_response(conn, 403)
  end

  test "does not renders form for editing chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = get student_conn, coordinator_path(student_conn, :edit, coordinator)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :edit, coordinator)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :edit, coordinator)
    assert html_response(conn, 403)
  end

  test "does not delete a chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = delete student_conn, coordinator_path(student_conn, :delete, coordinator.id)
    assert html_response(conn, 403)

    conn = delete teacher_conn, coordinator_path(teacher_conn, :delete, coordinator.id)
    assert html_response(conn, 403)

    conn = delete volunteer_conn, coordinator_path(volunteer_conn, :delete, coordinator.id)
    assert html_response(conn, 403)
  end

  test "does not render form for new coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, coordinator_path(student_conn, :new)
    assert html_response(conn, 403)

    conn = get teacher_conn, coordinator_path(teacher_conn, :new)
    assert html_response(conn, 403)

    conn = get volunteer_conn, coordinator_path(volunteer_conn, :new)
    assert html_response(conn, 403)
  end

  test "create coordinator for coordinator user", %{conn: conn} do
    conn = post conn, coordinator_path(conn, :create), %{"user" => %{"email" => "foo@bar.com"}}
    assert redirected_to(conn) == coordinator_path(conn, :index)
    conn = get conn, coordinator_path(conn, :index)
    assert html_response(conn, 200)
  end

  test "does not create coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator)

    conn = post student_conn, coordinator_path(student_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)

    conn = post teacher_conn, coordinator_path(teacher_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)

    conn = post volunteer_conn, coordinator_path(volunteer_conn, :create), %{"user" => coordinator}
    assert html_response(conn, 403)
  end

  test "does not update chosen coordinator for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    coordinator = insert(:coordinator, %{})

    conn = put student_conn, coordinator_path(student_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)

    conn = put teacher_conn, coordinator_path(teacher_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)

    conn = put volunteer_conn, coordinator_path(volunteer_conn, :update, coordinator), %{"user" => %{"email" => "foo@bar.com"}}
    assert html_response(conn, 403)
  end
end


Remove debug fn.

Remove debug fn.

defmodule LeatherWeb.PageController do
  @moduledoc false

  use LeatherWeb, :controller

  def index(conn, _params) do
    render(conn, "index.html")
  end
end

defmodule LeatherWeb.PageController do
  @moduledoc false

  use LeatherWeb, :controller

  def index(conn, _params) do
    render(conn, "index.html")
  end

  def thing(conn, _params) do
    render()
  end
end


fix: use newer version of translate_error with bugfix

defmodule Wwwtech.ErrorHelpers do
  @moduledoc """
  Conveniences for translating and building error messages.
  """

  use Phoenix.HTML

  @doc """
  Generates tag for inlined form input errors.
  """
  def error_tag(form, field) do
    if error = form.errors[field] do
      content_tag :span, translate_error(error), class: "help-block"
    end
  end

  @doc """
  Translates an error message using gettext.
  """
  def translate_error({msg, opts}) do
    # Because error messages were defined within Ecto, we must
    # call the Gettext module passing our Gettext backend. We
    # also use the "errors" domain as translations are placed
    # in the errors.po file.
    # Ecto will pass the :count keyword if the error message is
    # meant to be pluralized.
    # On your own code and templates, depending on whether you
    # need the message to be pluralized or not, this could be
    # written simply as:
    #
    #     dngettext "errors", "1 file", "%{count} files", count
    #     dgettext "errors", "is invalid"
    #
    if count = opts[:count] do
      Gettext.dngettext(Wwwtech.Gettext, "errors", msg, msg, count, opts)
    else
      Gettext.dgettext(Wwwtech.Gettext, "errors", msg, opts)
    end
  end
end

defmodule Wwwtech.ErrorHelpers do
  @moduledoc """
  Conveniences for translating and building error messages.
  """

  use Phoenix.HTML

  @doc """
  Generates tag for inlined form input errors.
  """
  def error_tag(form, field) do
    if error = form.errors[field] do
      content_tag :span, translate_error(error), class: "help-block"
    end
  end

  @doc """
  Translates an error message using gettext.
  """
  def translate_error({msg, opts}) do
    # Because error messages were defined within Ecto, we must
    # call the Gettext module passing our Gettext backend. We
    # also use the "errors" domain as translations are placed
    # in the errors.po file. On your own code and templates,
    # this could be written simply as:
    #
    #     dngettext "errors", "1 file", "%{count} files", count
    #
    Gettext.dngettext(Wwwtech.Gettext, "errors", msg, msg, opts[:count], opts)
  end

  def translate_error(msg) do
    Gettext.dgettext(Wwwtech.Gettext, "errors", msg)
  end
end


Removed references to continuations in tests

# Copyright 2016 Apcera Inc. All rights reserved.
# this is somewhat generated. don't touch.
defmodule Nats.ParserTest do
  use ExUnit.Case, async: true
  import TestHelper

  defp encode(x),             do: Enum.join(Nats.Parser.encode(x), "")
  defp parse(binary),         do: Nats.Parser.parse(binary)
  defp parse(state, binary),  do: Nats.Parser.parse(state, binary)

   test "PING/PONG/OK/ERR parsing" do
    assert_verb_parse_encode("PING\r\n", {:ping})
    assert_verb_parse_encode("PONG\r\n", {:pong})
    assert_verb_parse_encode("+OK\r\n", {:ok})
    assert_verb_parse_encode("-ERR abc\r\n", {:err, "abc"})
    assert_verb_parse_encode("-ERR hello world\r\n", {:err, "hello world"})

    # missing arg...
    assert_parse_error("-ERR\r\n")
  end

  test "INFO/CONNECT parsing" do
    assert_parse_error("INFO \r\n")
    assert_parse_error("INFO \"false\" \r\n")
    assert_parse_error("INFO false \r\n")
    assert_parse_error("INFO \"FFFF\" \r\n")
    assert_parse_error("INFO 456 \r\n")
    assert_parse_error("INFO true\r\n")

    assert_parses(
      binary:   "INFO {\"key\":true}\r\n",
      expected: {:info, %{"key" => true}},
      encoded:  "INFO {\"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"key\":true, \"embed\": {\"a\": [\"b\",\"c\", 123] } }\r\n",
      expected: {:info, %{"embed" => %{"a" => ["b", "c", 123]}, "key" => true}},
      encoded:  "INFO {\"embed\": {\"a\": [\"b\", \"c\", 123]}, \"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO {}\r\n",
      expected: {:info, %{}},
      encoded:  "INFO {}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"key\":true}\r\n",
      expected: {:info, %{"key" => true}},
      encoded:  "INFO {\"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"k1\":true, \"k2\": false}\r\n",
      expected: {:info, %{"k1" => true, "k2" => false}},
      encoded:  "INFO {\"k1\": true, \"k2\": false}\r\n",
    )

    assert_parses(
      binary:   "CONNECT {\"key\":true}\r\n",
      expected: {:connect, %{"key" => true}},
      encoded:  "CONNECT {\"key\": true}\r\n",
    )

    assert_parse_error("INFO []\r\n")
    assert_parse_error("INFO [\r\n")
    assert_parse_error("INFO @\r\n")
    assert_parse_error("INFO [false, true,false,]\r\n")

    {:cont, _howmany, state} = parse("CON")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NECT {}\r\n")

    {:ok, _, "+OK\r\n", _} = parse("PUB S S 1\r\n1\r\n+OK\r\n")
    {:ok, _, "", _} = parse("PUB S S 1\r\n1\r\n")

    assert_parse_error("PUB S S 1\r\n1\rZ+OK\r\n")

    assert_parses(
      binary:   "INFO {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:info, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "INFO {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )

    assert_parses(
      binary:   "INFO {\"a\": [true,false,null,\"abc\",[1],2.2,[]]}\r\n",
      expected: {:info, %{"a" => [true, false, nil, "abc", [1], 2.2, []]}},
      encoded:  "INFO {\"a\": [true, false, null, \"abc\", [1], 2.2, []]}\r\n",
    )

    assert_parses(
      binary:   "INFO {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:info, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "INFO {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )

    assert_parses(
      binary:   "CONNECT {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:connect, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "CONNECT {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )
  end

  test "UNSUB parsing" do
    assert_parses(
      binary:   "UNSUB sid\r\n",
      expected: {:unsub, "sid", nil},
    )
    assert_parses(
      binary:   "UNSUB sid 10\r\n",
      expected: {:unsub, "sid", 10},
    )
    assert_parse_error("UNSUB sid bad\r\n")
  end

  test "SUB parsing" do
    assert_parse_error("SUB bad\r\n")

    assert_parses(
      binary:   "SUB subj sid\r\n",
      expected: {:sub, "subj", nil, "sid"},
    )

    assert_parses(
      binary:   "SUB subj q sid\r\n",
      expected: {:sub, "subj", "q", "sid"},
    )

    assert_parses(
      binary:   "SUB S s\r\n",
      expected: {:sub, "S", nil, "s"},
    )

    assert_parses(
      binary:   "SUB S Q s\r\n",
      expected: {:sub, "S", "Q", "s"},
    )
  end

  test "PUB parsing" do
    assert_parses(
      binary:   "PUB subj 0\r\n\r\n",
      expected: {:pub, "subj", nil, ""},
    )
    assert_parses(
      binary:   "PUB subj 4\r\n1234\r\n",
      expected: {:pub, "subj", nil, "1234"},
    )
    assert_parses(
      binary:   "PUB subj ret 4\r\nnats\r\n",
      expected: {:pub, "subj", "ret", "nats"},
    )
    assert_parses(
      binary:   "PUB subj ret 2\r\nio\r\n",
      expected: {:pub, "subj", "ret", "io"},
    )
    assert_parses(
      binary:   "PUB subj ret 5\r\nnats!\r\n",
      expected: {:pub, "subj", "ret", "nats!"},
    )
    assert_parses(
      binary:   "PUB subj ret 5\r\nnats!\r\n",
      expected: {:pub, "subj", "ret", "nats!"},
    )
    assert_parses(
      binary:   "PUB subj ret 10\r\nhello nats\r\n",
      expected: {:pub, "subj", "ret", "hello nats"},
    )

    assert_parse_error("PUB subj ret -1\r\n")
    assert_parse_error("PUB sub ret zz\r\n")
    assert_parse_error("PUB sub zz\r\n")
    assert_parse_error("PUB zz\r\n")
    assert_parse_error("PUB \r\n")

    assert {:cont, 2, _} = parse("PUB sub ret 0\r\n")
    assert {:cont, 2, _}   = parse("PUB sub 0\r\n")
    assert {:cont, 6, _} = parse("PUB sub ret 4\r\n")
    assert {:cont, 2, _} = parse("PUB sub ret 4\r\nhell")

    assert_parse_error("PUB 0\r\n")
    assert_parse_error("PUB \r\n")

    assert_parses(
      binary:   "PUB S 0\r\n\r\n",
      expected: {:pub, "S", nil, ""},
    )
    assert_parses(
      binary:   "PUB S R 0\r\n\r\n",
      expected: {:pub, "S", "R", ""},
    )
  end

  test "MSG parsing" do
    assert_parses(
      binary:   "MSG subj sid 0\r\n\r\n",
      expected: {:msg, "subj", "sid", nil, ""},
    )
    assert_parses(
      binary:   "MSG subj sid ret 4\r\nnats\r\n",
      expected: {:msg, "subj", "sid", "ret", "nats"},
    )

    assert_parse_error("MSG subj sid ret bad\r\n")
    assert_parse_error("MSG subj sid ret -1\r\n")
    assert_parse_error("MSG subj zz\r\n")
    assert_parse_error("MSG zz\r\n")
    assert_parse_error("MSG \r\n")

    assert_parses(
      binary:   "MSG S s 0\r\n\r\n",
      expected: {:msg, "S", "s", nil, ""},
    )
    assert_parses(
      binary:   "MSG S s R 0\r\n\r\n",
      expected: {:msg, "S", "s", "R", ""},
    )

    assert {:cont, 2, _} = parse("MSG sub ret 0\r\n")

    assert_parse_error("MSG sub 0\r\n")
    assert_parse_error("MSG 0\r\n")
    assert_parse_error("MSG \r\n")
  end

  test "continuation testing to address GH-18" do
    # Thanks @mindreframer !
    # See https://github.com/nats-io/elixir-nats/issues/18

    {:cont, _, state} = parse("CO")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NNECT {}\r\n")

    {:cont, _, state} = parse("CON")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NECT {}\r\n")

    {:cont, _, state} = parse("CONN")
    {:ok, {:connect, %{}}, "", _} = parse(state, "ECT {}\r\n")

    {:cont, _, state} = parse("CONNE")
    {:ok, {:connect, %{}}, "", _} = parse(state, "CT {}\r\n")

    {:cont, _, state} = parse("CONNE")
    {:ok, {:connect, %{}}, "", _} = parse(state, "CT {}\r\n")

    {:cont, _, state} = parse("-E")
    {:cont, _, state} = parse(state, "R")
    {:cont, _, state} = parse(state, "R")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "")
    msg = "SOME ERROR"
    {:cont, _, state} = parse(state, msg)
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    msg = " " <> msg
    {:ok, {:err, ^msg}, "", _state} = parse(state, "\n")

    {:cont, _, state} = parse("MSG ")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "s")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "u")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "b")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " SID1.")
    {:cont, _, state} = parse(state, "SID2.")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "S")
    {:cont, _, state} = parse(state, "I")
    {:cont, _, state} = parse(state, "D3")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\t")
    {:cont, _, state} = parse(state, " \t ")
    {:cont, _, state} = parse(state, "4")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "\t")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\n")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "na")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "t")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "s")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    {:ok, verb, "ABC", _state} = parse(state, "\nABC")
    assert verb === {:msg, "sub", "SID1.SID2.SID3", nil, "nats"}
  end
end

# Copyright 2016 Apcera Inc. All rights reserved.
# this is somewhat generated. don't touch.
defmodule Nats.ParserTest do
  use ExUnit.Case, async: true
  import TestHelper

  defp encode(x),             do: Enum.join(Nats.Parser.encode(x), "")
  defp parse(binary),         do: Nats.Parser.parse(binary)
  defp parse(state, binary),  do: Nats.Parser.parse(state, binary)

   test "PING/PONG/OK/ERR parsing" do
    assert_verb_parse_encode("PING\r\n", {:ping})
    assert_verb_parse_encode("PONG\r\n", {:pong})
    assert_verb_parse_encode("+OK\r\n", {:ok})
    assert_verb_parse_encode("-ERR abc\r\n", {:err, "abc"})
    assert_verb_parse_encode("-ERR hello world\r\n", {:err, "hello world"})

    # missing arg...
    assert_parse_error("-ERR\r\n")
  end

  test "INFO/CONNECT parsing" do
    assert_parse_error("INFO \r\n")
    assert_parse_error("INFO \"false\" \r\n")
    assert_parse_error("INFO false \r\n")
    assert_parse_error("INFO \"FFFF\" \r\n")
    assert_parse_error("INFO 456 \r\n")
    assert_parse_error("INFO true\r\n")

    assert_parses(
      binary:   "INFO {\"key\":true}\r\n",
      expected: {:info, %{"key" => true}},
      encoded:  "INFO {\"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"key\":true, \"embed\": {\"a\": [\"b\",\"c\", 123] } }\r\n",
      expected: {:info, %{"embed" => %{"a" => ["b", "c", 123]}, "key" => true}},
      encoded:  "INFO {\"embed\": {\"a\": [\"b\", \"c\", 123]}, \"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO {}\r\n",
      expected: {:info, %{}},
      encoded:  "INFO {}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"key\":true}\r\n",
      expected: {:info, %{"key" => true}},
      encoded:  "INFO {\"key\": true}\r\n",
    )

    assert_parses(
      binary:   "INFO { \"k1\":true, \"k2\": false}\r\n",
      expected: {:info, %{"k1" => true, "k2" => false}},
      encoded:  "INFO {\"k1\": true, \"k2\": false}\r\n",
    )

    assert_parses(
      binary:   "CONNECT {\"key\":true}\r\n",
      expected: {:connect, %{"key" => true}},
      encoded:  "CONNECT {\"key\": true}\r\n",
    )

    assert_parse_error("INFO []\r\n")
    assert_parse_error("INFO [\r\n")
    assert_parse_error("INFO @\r\n")
    assert_parse_error("INFO [false, true,false,]\r\n")

    {:cont, _howmany, state} = parse("CON")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NECT {}\r\n")

    {:ok, _, "+OK\r\n", _} = parse("PUB S S 1\r\n1\r\n+OK\r\n")
    {:ok, _, "", _} = parse("PUB S S 1\r\n1\r\n")

    assert_parse_error("PUB S S 1\r\n1\rZ+OK\r\n")

    assert_parses(
      binary:   "INFO {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:info, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "INFO {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )

    assert_parses(
      binary:   "INFO {\"a\": [true,false,null,\"abc\",[1],2.2,[]]}\r\n",
      expected: {:info, %{"a" => [true, false, nil, "abc", [1], 2.2, []]}},
      encoded:  "INFO {\"a\": [true, false, null, \"abc\", [1], 2.2, []]}\r\n",
    )

    assert_parses(
      binary:   "INFO {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:info, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "INFO {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )

    assert_parses(
      binary:   "CONNECT {\"a\":{\"b\":{\"c\":\"zebra\"}}}\r\n",
      expected: {:connect, %{"a" => %{"b" => %{"c" => "zebra"}}}},
      encoded:  "CONNECT {\"a\": {\"b\": {\"c\": \"zebra\"}}}\r\n",
    )
  end

  test "UNSUB parsing" do
    assert_parses(
      binary:   "UNSUB sid\r\n",
      expected: {:unsub, "sid", nil},
    )
    assert_parses(
      binary:   "UNSUB sid 10\r\n",
      expected: {:unsub, "sid", 10},
    )
    assert_parse_error("UNSUB sid bad\r\n")
  end

  test "SUB parsing" do
    assert_parse_error("SUB bad\r\n")

    assert_parses(
      binary:   "SUB subj sid\r\n",
      expected: {:sub, "subj", nil, "sid"},
    )

    assert_parses(
      binary:   "SUB subj q sid\r\n",
      expected: {:sub, "subj", "q", "sid"},
    )

    assert_parses(
      binary:   "SUB S s\r\n",
      expected: {:sub, "S", nil, "s"},
    )

    assert_parses(
      binary:   "SUB S Q s\r\n",
      expected: {:sub, "S", "Q", "s"},
    )
  end

  test "PUB parsing" do
    assert_parses(
      binary:   "PUB subj 0\r\n\r\n",
      expected: {:pub, "subj", nil, ""},
    )
    assert_parses(
      binary:   "PUB subj 4\r\n1234\r\n",
      expected: {:pub, "subj", nil, "1234"},
    )
    assert_parses(
      binary:   "PUB subj ret 4\r\nnats\r\n",
      expected: {:pub, "subj", "ret", "nats"},
    )
    assert_parses(
      binary:   "PUB subj ret 2\r\nio\r\n",
      expected: {:pub, "subj", "ret", "io"},
    )
    assert_parses(
      binary:   "PUB subj ret 5\r\nnats!\r\n",
      expected: {:pub, "subj", "ret", "nats!"},
    )
    assert_parses(
      binary:   "PUB subj ret 5\r\nnats!\r\n",
      expected: {:pub, "subj", "ret", "nats!"},
    )
    assert_parses(
      binary:   "PUB subj ret 10\r\nhello nats\r\n",
      expected: {:pub, "subj", "ret", "hello nats"},
    )

    assert_parse_error("PUB subj ret -1\r\n")
    assert_parse_error("PUB sub ret zz\r\n")
    assert_parse_error("PUB sub zz\r\n")
    assert_parse_error("PUB zz\r\n")
    assert_parse_error("PUB \r\n")

    assert {:cont, 2, {_, "", {0, {:pub, "sub", "ret", 0}, ""}}} = parse("PUB sub ret 0\r\n")
    assert {:cont, 2, {_, "", {0, {:pub, "sub", nil, 0}, ""}}}   = parse("PUB sub 0\r\n")

    assert_parse_error("PUB 0\r\n")
    assert_parse_error("PUB \r\n")

    assert_parses(
      binary:   "PUB S 0\r\n\r\n",
      expected: {:pub, "S", nil, ""},
    )
    assert_parses(
      binary:   "PUB S R 0\r\n\r\n",
      expected: {:pub, "S", "R", ""},
    )
  end

  test "MSG parsing" do
    assert_parses(
      binary:   "MSG subj sid 0\r\n\r\n",
      expected: {:msg, "subj", "sid", nil, ""},
    )
    assert_parses(
      binary:   "MSG subj sid ret 4\r\nnats\r\n",
      expected: {:msg, "subj", "sid", "ret", "nats"},
    )

    assert_parse_error("MSG subj sid ret bad\r\n")
    assert_parse_error("MSG subj sid ret -1\r\n")
    assert_parse_error("MSG subj zz\r\n")
    assert_parse_error("MSG zz\r\n")
    assert_parse_error("MSG \r\n")

    assert_parses(
      binary:   "MSG S s 0\r\n\r\n",
      expected: {:msg, "S", "s", nil, ""},
    )
    assert_parses(
      binary:   "MSG S s R 0\r\n\r\n",
      expected: {:msg, "S", "s", "R", ""},
    )

    assert {:cont, 2, {_, "", {0, {:msg, "sub", "ret", nil, 0}, ""}}} = parse("MSG sub ret 0\r\n")

    assert_parse_error("MSG sub 0\r\n")
    assert_parse_error("MSG 0\r\n")
    assert_parse_error("MSG \r\n")
  end

  test "continuation testing to address GH-18" do
    # Thanks @mindreframer !
    # See https://github.com/nats-io/elixir-nats/issues/18

    {:cont, _, state} = parse("CO")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NNECT {}\r\n")

    {:cont, _, state} = parse("CON")
    {:ok, {:connect, %{}}, "", _} = parse(state, "NECT {}\r\n")

    {:cont, _, state} = parse("CONN")
    {:ok, {:connect, %{}}, "", _} = parse(state, "ECT {}\r\n")

    {:cont, _, state} = parse("CONNE")
    {:ok, {:connect, %{}}, "", _} = parse(state, "CT {}\r\n")

    {:cont, _, state} = parse("CONNE")
    {:ok, {:connect, %{}}, "", _} = parse(state, "CT {}\r\n")

    {:cont, _, state} = parse("-E")
    {:cont, _, state} = parse(state, "R")
    {:cont, _, state} = parse(state, "R")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "")
    msg = "SOME ERROR"
    {:cont, _, state} = parse(state, msg)
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    msg = " " <> msg
    {:ok, {:err, ^msg}, "", _state} = parse(state, "\n")

    {:cont, _, state} = parse("MSG ")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "s")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "u")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "b")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, " SID1.")
    {:cont, _, state} = parse(state, "SID2.")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "S")
    {:cont, _, state} = parse(state, "I")
    {:cont, _, state} = parse(state, "D3")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\t")
    {:cont, _, state} = parse(state, " \t ")
    {:cont, _, state} = parse(state, "4")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, " ")
    {:cont, _, state} = parse(state, "\t")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\n")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "na")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "t")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "s")
    {:cont, _, state} = parse(state, "")
    {:cont, _, state} = parse(state, "\r")
    {:cont, _, state} = parse(state, "")
    {:ok, verb, "ABC", _state} = parse(state, "\nABC")
    assert verb === {:msg, "sub", "SID1.SID2.SID3", nil, "nats"}
  end
end


insert test that captures #4

insert test that captures #4

defmodule PtonWeb.LinkControllerTest do
  use PtonWeb.ConnCase

  alias Pton.Redirection
  alias Pton.Accounts
  alias Pton.Repo

  @create_attrs %{slug: "some-slug", url: "http://google.com"}
  @update_attrs %{slug: "some-updated-slug", url: "http://yahoo.com"}
  @invalid_attrs %{slug: nil, url: nil}

  describe "index" do
    setup [:create_link]

    test "lists all links", %{conn: conn, link: link} do
      conn = get conn, link_path(conn, :index)
      assert html_response(conn, 200) =~ "All Links"
      assert html_response(conn, 200) =~ link.slug
    end
  end

  describe "my links" do
    setup [:create_link_with_owner]

    test "lists only links owned by the current user", %{conn: conn, link: link, user: user} do
      other_link = insert(:link)
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :mine))

      assert html_response(conn, 200) =~ "My Links"
      assert html_response(conn, 200) =~ link.slug
      assert not(html_response(conn, 200) =~ other_link.slug)
    end
  end

  describe "new link" do
    setup [:create_user]

    test "renders form", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :new))

      assert html_response(conn, 200) =~ "New Link"
    end

    test "renders login button if user not logged in", %{conn: conn} do
      conn = conn
      |> get(link_path(conn, :new))

      assert not(html_response(conn, 200) =~ "enter a url")
    end
  end

  describe "create link" do
    setup [:create_user]

    test "redirects to show when data is valid", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @create_attrs)

      assert %{id: id} = redirected_params(conn)
      assert redirected_to(conn) == link_path(conn, :show, id)

      conn = get conn, link_path(conn, :show, id)
      assert html_response(conn, 200) =~ "Show Link"
    end

    test "does not create link without url", %{conn: conn, user: user} do
      conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: Map.delete(@create_attrs, :url))

      assert length(Redirection.list_links) == 0
    end

    test "creates a random slug when no slug exists", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: Map.delete(@create_attrs, :slug))

      %{id: id} = redirected_params(conn)

      link = Redirection.get_link!(id)
      assert not(is_nil(link.slug))
    end

    test "includes current user as owner on creation", %{conn: conn, user: user} do
      conn =  conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @create_attrs)

      %{id: id} = redirected_params(conn)

      link = Redirection.get_link!(id)
      users = Repo.all(Ecto.assoc(link, :owners))

      assert length(users) == 1
      assert Enum.fetch!(users, 0).id == user.id
    end

    test "renders errors when data is invalid", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @invalid_attrs)

      assert html_response(conn, 200) =~ "New Link"
    end

    test "validates urls on creation", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: %{@create_attrs | url: "http://'; DROP TABLE links;.com"})

      assert html_response(conn, 200) =~ "New Link"
    end
  end

  describe "show link" do
    setup [:create_link_with_owner]

    test "shows edit button if user owns link", %{conn: conn, user: user, link: link} do
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert html_response(conn, 200) =~ "Edit Link"
    end

    test "hides edit button if user does not own link", %{conn: conn, link: link} do
      user = insert(:user)

      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert not(html_response(conn, 200) =~ "Edit Link")
    end

    test "shows owner if user is logged in", %{conn: conn, link: link} do
      user = insert(:user)

      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert html_response(conn, 200) =~ user.netid
    end

    test "hides owner if user is not logged in", %{conn: conn, link: link, user: user} do
      conn = conn |> get(link_path(conn, :show, link))

      assert not(html_response(conn, 200) =~ user.netid)
    end
  end

  describe "edit link" do
    setup [:create_link_with_owner]

    test "renders form for editing chosen link", %{conn: conn, user: user, link: link} do
      conn =  conn
      |> assign(:user, user)
      |> get(link_path(conn, :edit, link))

      assert html_response(conn, 200) =~ "Edit Link"
    end
  end

  describe "follow link" do
    setup [:create_link]

    test "redirects to destination", %{conn: conn, link: link} do
      conn = get conn, link_path(conn, :follow, link.slug)
      assert redirected_to(conn) == link.url
    end
  end

  describe "update link" do
    setup [:create_link_with_owner]

    test "redirects when data is valid", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> put(link_path(conn, :update, link), link: @update_attrs)

      assert redirected_to(conn) == link_path(conn, :show, link)

      conn = get conn, link_path(conn, :show, link)
      assert html_response(conn, 200) =~ "some-updated-slug"
    end

    test "renders errors when data is invalid", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> put(link_path(conn, :update, link), link: @invalid_attrs)

      assert html_response(conn, 200) =~ "Edit Link"
    end

    test "validates that the user is an owner before updating", %{conn: conn, link: link, user: user} do
      other_user = insert(:user)
      assert other_user.id != user.id

      conn = conn
      |> assign(:user, other_user)
      |> put(link_path(conn, :update, link), link: @update_attrs)

      assert get_flash(conn, :error) =~ "not authorized"
    end
  end

  describe "delete link" do
    setup [:create_link_with_owner]

    test "deletes chosen link", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> delete(link_path(conn, :delete, link))
      assert redirected_to(conn) == link_path(conn, :index)

      assert_error_sent 404, fn ->
        conn
        |> assign(:user, user)
        |> get(link_path(conn, :show, link))
      end
    end

    test "deletes only the corresponding link", %{conn: conn, link: link, user: user} do
      insert_list(2, :link, %{ owners: [user] })
      num_users = length(Accounts.list_users())

      conn
      |> assign(:user, user)
      |> delete(link_path(conn, :delete, link))

      assert length(Accounts.list_users()) == num_users
      assert length(Redirection.list_links()) == 2
    end

    test "validates that the user is an owner before deleting", %{conn: conn, link: link, user: user} do
      num_links = length(Redirection.list_links())
      other_user = insert(:user)
      assert other_user.id != user.id

      conn = conn
      |> assign(:user, other_user)
      |> delete(link_path(conn, :delete, link))

      assert get_flash(conn, :error) =~ "not authorized"
      assert length(Redirection.list_links()) == num_links
    end
  end

  defp create_user(_) do
    user = insert(:user)
    {:ok, user: user}
  end

  defp create_link(_) do
    link = insert(:link)
    {:ok, link: link}
  end

  defp create_link_with_owner(_) do
    user = insert(:user)
    link = insert(:link, owners: [user])
    {:ok, user: user, link: link}
  end

end

defmodule PtonWeb.LinkControllerTest do
  use PtonWeb.ConnCase

  alias Pton.Redirection
  alias Pton.Accounts
  alias Pton.Repo

  @create_attrs %{slug: "some-slug", url: "http://google.com"}
  @update_attrs %{slug: "some-updated-slug", url: "http://yahoo.com"}
  @invalid_attrs %{slug: nil, url: nil}

  describe "index" do
    setup [:create_link]

    test "lists all links", %{conn: conn, link: link} do
      conn = get conn, link_path(conn, :index)
      assert html_response(conn, 200) =~ "All Links"
      assert html_response(conn, 200) =~ link.slug
    end
  end

  describe "my links" do
    setup [:create_link_with_owner]

    test "lists only links owned by the current user", %{conn: conn, link: link, user: user} do
      other_link = insert(:link)
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :mine))

      assert html_response(conn, 200) =~ "My Links"
      assert html_response(conn, 200) =~ link.slug
      assert not(html_response(conn, 200) =~ other_link.slug)
    end
  end

  describe "new link" do
    setup [:create_user]

    test "renders form", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :new))

      assert html_response(conn, 200) =~ "New Link"
    end

    test "renders login button if user not logged in", %{conn: conn} do
      conn = conn
      |> get(link_path(conn, :new))

      assert not(html_response(conn, 200) =~ "enter a url")
    end
  end

  describe "create link" do
    setup [:create_user]

    test "redirects to show when data is valid", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @create_attrs)

      assert %{id: id} = redirected_params(conn)
      assert redirected_to(conn) == link_path(conn, :show, id)

      conn = get conn, link_path(conn, :show, id)
      assert html_response(conn, 200) =~ "Show Link"
    end

    test "does not create link without url", %{conn: conn, user: user} do
      conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: Map.delete(@create_attrs, :url))

      assert length(Redirection.list_links) == 0
    end

    test "creates a random slug when no slug exists", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: Map.delete(@create_attrs, :slug))

      %{id: id} = redirected_params(conn)

      link = Redirection.get_link!(id)
      assert not(is_nil(link.slug))
    end

    test "includes current user as owner on creation", %{conn: conn, user: user} do
      conn =  conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @create_attrs)

      %{id: id} = redirected_params(conn)

      link = Redirection.get_link!(id)
      users = Repo.all(Ecto.assoc(link, :owners))

      assert length(users) == 1
      assert Enum.fetch!(users, 0).id == user.id
    end

    test "renders errors when data is invalid", %{conn: conn, user: user} do
      conn = conn
      |> assign(:user, user)
      |> post(link_path(conn, :create), link: @invalid_attrs)

      assert html_response(conn, 200) =~ "New Link"
    end
  end

  describe "show link" do
    setup [:create_link_with_owner]

    test "shows edit button if user owns link", %{conn: conn, user: user, link: link} do
      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert html_response(conn, 200) =~ "Edit Link"
    end

    test "hides edit button if user does not own link", %{conn: conn, link: link} do
      user = insert(:user)

      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert not(html_response(conn, 200) =~ "Edit Link")
    end

    test "shows owner if user is logged in", %{conn: conn, link: link} do
      user = insert(:user)

      conn = conn
      |> assign(:user, user)
      |> get(link_path(conn, :show, link))

      assert html_response(conn, 200) =~ user.netid
    end

    test "hides owner if user is not logged in", %{conn: conn, link: link, user: user} do
      conn = conn |> get(link_path(conn, :show, link))

      assert not(html_response(conn, 200) =~ user.netid)
    end
  end

  describe "edit link" do
    setup [:create_link_with_owner]

    test "renders form for editing chosen link", %{conn: conn, user: user, link: link} do
      conn =  conn
      |> assign(:user, user)
      |> get(link_path(conn, :edit, link))

      assert html_response(conn, 200) =~ "Edit Link"
    end
  end

  describe "follow link" do
    setup [:create_link]

    test "redirects to destination", %{conn: conn, link: link} do
      conn = get conn, link_path(conn, :follow, link.slug)
      assert redirected_to(conn) == link.url
    end
  end

  describe "update link" do
    setup [:create_link_with_owner]

    test "redirects when data is valid", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> put(link_path(conn, :update, link), link: @update_attrs)

      assert redirected_to(conn) == link_path(conn, :show, link)

      conn = get conn, link_path(conn, :show, link)
      assert html_response(conn, 200) =~ "some-updated-slug"
    end

    test "renders errors when data is invalid", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> put(link_path(conn, :update, link), link: @invalid_attrs)

      assert html_response(conn, 200) =~ "Edit Link"
    end

    test "validates that the user is an owner before updating", %{conn: conn, link: link, user: user} do
      other_user = insert(:user)
      assert other_user.id != user.id

      conn = conn
      |> assign(:user, other_user)
      |> put(link_path(conn, :update, link), link: @update_attrs)

      assert get_flash(conn, :error) =~ "not authorized"
    end
  end

  describe "delete link" do
    setup [:create_link_with_owner]

    test "deletes chosen link", %{conn: conn, link: link, user: user} do
      conn = conn
      |> assign(:user, user)
      |> delete(link_path(conn, :delete, link))
      assert redirected_to(conn) == link_path(conn, :index)

      assert_error_sent 404, fn ->
        conn
        |> assign(:user, user)
        |> get(link_path(conn, :show, link))
      end
    end

    test "deletes only the corresponding link", %{conn: conn, link: link, user: user} do
      insert_list(2, :link, %{ owners: [user] })
      num_users = length(Accounts.list_users())

      conn
      |> assign(:user, user)
      |> delete(link_path(conn, :delete, link))

      assert length(Accounts.list_users()) == num_users
      assert length(Redirection.list_links()) == 2
    end

    test "validates that the user is an owner before deleting", %{conn: conn, link: link, user: user} do
      num_links = length(Redirection.list_links())
      other_user = insert(:user)
      assert other_user.id != user.id

      conn = conn
      |> assign(:user, other_user)
      |> delete(link_path(conn, :delete, link))

      assert get_flash(conn, :error) =~ "not authorized"
      assert length(Redirection.list_links()) == num_links
    end
  end

  defp create_user(_) do
    user = insert(:user)
    {:ok, user: user}
  end

  defp create_link(_) do
    link = insert(:link)
    {:ok, link: link}
  end

  defp create_link_with_owner(_) do
    user = insert(:user)
    link = insert(:link, owners: [user])
    {:ok, user: user, link: link}
  end

end


fix: hack docs link for admin

fix: hack docs link for admin

# Copyright 2019 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule GoogleApis.Generator.ElixirGenerator do
  @moduledoc """
  Code generator written in Elixir which takes a Google Discovery document and
  generates an Elixir client library.
  """

  @behaviour GoogleApis.Generator
  alias GoogleApis.ApiConfig
  alias GoogleApi.Discovery.V1.Model.RestDescription

  alias GoogleApis.Generator.ElixirGenerator.{
    Api,
    Endpoint,
    Model,
    Parameter,
    Renderer,
    ResourceContext,
    Token
  }

  @doc """
  Run the generator for the specified api configuration
  """
  @spec generate_client(ApiConfig.t()) :: {:ok, any()} | {:error, String.t()}
  def generate_client(api_config) do
    Token.build(api_config)
    |> load_models
    |> set_model_filenames
    |> update_model_properties
    |> create_directories
    |> write_model_files
    |> load_global_optional_params
    |> load_apis
    |> set_api_filenames
    |> write_api_files
    |> write_connection
    |> write_mix_exs
    |> write_readme
    |> write_license
    |> write_gitignore
    |> write_config_exs
    |> write_test_helper_exs
  end

  defp load_models(token) do
    models = all_models(token.rest_description)

    token
    |> Map.put(:models, models)
    |> Map.put(
      :models_by_name,
      Enum.reduce(models, %{}, fn model, acc -> Map.put(acc, model.name, model) end)
    )
  end

  defp set_model_filenames(token) do
    {models, _} =
      token.models
      |> Enum.map_reduce(MapSet.new(), fn(model, used) ->
        {file, used} = find_unused_filename(Model.filename(model), used, 0)
        {%Model{model | filename: file}, used}
      end)
    %Token{token | models: models}
  end

  defp set_api_filenames(token) do
    {apis, _} =
      token.apis
      |> Enum.map_reduce(MapSet.new(), fn(api, used) ->
        {file, used} = find_unused_filename(Api.filename(api), used, 0)
        {%Api{api | filename: file}, used}
      end)
    %Token{token | apis: apis}
  end

  defp find_unused_filename(name, used, num) do
    candidate = filename_candidate(name, num)
    if MapSet.member?(used, candidate) do
      find_unused_filename(name, used, num + 1)
    else
      {candidate, MapSet.put(used, candidate)}
    end
  end

  def filename_candidate(name, 0), do: name

  def filename_candidate(name, num) do
    base = Path.basename(name, ".ex")
    "#{base}_#{num}.ex"
  end

  defp update_model_properties(token) do
    Map.update!(token, :models, fn models ->
      models
      |> Enum.map(fn model ->
        Model.update_properties(model, token.resource_context)
      end)
    end)
  end

  defp create_directories(token) do
    IO.puts("Creating leading directories")
    File.rm_rf!(token.base_dir)
    File.rm_rf!(Path.join(token.root_dir, ".swagger-codegen"))
    File.rm_rf!(Path.join(token.root_dir, ".swagger-codegen-ignore"))
    File.mkdir_p!(Path.join(token.base_dir, "api"))
    File.mkdir_p!(Path.join(token.base_dir, "model"))
    File.mkdir_p!(Path.join(token.root_dir, "config"))
    File.mkdir_p!(Path.join(token.root_dir, "test"))
    token
  end

  defp write_connection(token) do
    scopes = scopes_for(token.rest_description)
    otp_app = "google_api_#{token.library_name}"

    path = Path.join(token.base_dir, "connection.ex")
    IO.puts("Writing connection.ex.")

    File.write!(
      path,
      Renderer.connection(token.namespace, scopes, otp_app, token.base_url)
    )

    token
  end

  defp write_license(token) do
    path = Path.join(token.root_dir, "LICENSE")
    IO.puts("Writing LICENSE")
    File.write!(path, Renderer.license())

    token
  end

  defp write_config_exs(token) do
    path = Path.join(token.root_dir, "config/config.exs")
    IO.puts("Writing config/config.exs")
    has_env_config = token.root_dir |> Path.join("config/dev.exs") |> File.regular?()
    File.write!(path, Renderer.config_exs(has_env_config))

    token
  end

  defp write_test_helper_exs(token) do
    path = Path.join(token.root_dir, "test/test_helper.exs")
    IO.puts("Writing test/test_helper.exs")
    File.write!(path, Renderer.test_helper_exs(token.root_namespace))

    token
  end

  defp write_gitignore(token) do
    path = Path.join(token.root_dir, ".gitignore")
    IO.puts("Writing .gitignore")
    File.write!(path, Renderer.gitignore())

    token
  end

  defp write_mix_exs(token) do
    path = Path.join(token.root_dir, "mix.exs")
    IO.puts("Writing mix.exs")

    description = generate_hex_description(token.rest_description)
    docs_link = docs_link_for(token.rest_description)
    version = version_from_mix_exs(path)

    File.write!(
      path,
      Renderer.mix_exs(
        token.root_namespace,
        token.library_name,
        docs_link,
        version,
        description
      )
    )

    token
  end

  defp write_readme(token) do
    path = Path.join(token.root_dir, "README.md")
    IO.puts("Writing README.md")

    api_title = api_title_for(token.rest_description)
    docs_link = docs_link_for(token.rest_description)
    description = description_for(token.rest_description)
    version = token.root_dir |> Path.join("mix.exs") |> version_from_mix_exs() |> Version.parse!()
    version_requirement = "~> #{version.major}.#{version.minor}"
    library_name = "google_api_#{token.library_name}"

    File.write!(
      path,
      Renderer.readme(
        token.root_namespace,
        library_name,
        api_title,
        docs_link,
        version_requirement,
        description
      )
    )

    token
  end

  defp generate_hex_description(rest_info) do
    api_title = api_title_for(rest_info)
    description_start = "#{api_title} client library."
    rest_description = description_for(rest_info)

    if String.length(description_start) + String.length(rest_description) > 298 do
      description_start
    else
      "#{description_start} #{rest_description}"
    end
  end

  defp scopes_for(%{auth: nil}), do: []
  defp scopes_for(%{auth: %{oauth2: %{scopes: scopes}}}), do: scopes

  defp api_title_for(%{title: title}) when is_binary(title), do: title

  defp docs_link_for(%{documentationLink: "/admin-sdk/" <> _ = link}), do: "https://developers.google.com#{link}"
  defp docs_link_for(%{documentationLink: link}) when is_binary(link), do: link
  defp docs_link_for(_), do: "https://cloud.google.com/"

  defp description_for(%{description: desc}) when is_binary(desc), do: desc
  defp description_for(_), do: ""

  defp version_from_mix_exs(path, default_version \\ "0.1.0") do
    with {:ok, old_content} <- File.read(path),
         [_, version] <- Regex.run(~r{@version "([\d\.]+)"}, old_content) do
      version
    else
      _ -> default_version
    end
  end

  defp write_model_files(%{models: models, namespace: namespace, base_dir: base_dir} = token) do
    models
    |> Enum.each(fn model ->
      path = Path.join([base_dir, "model", model.filename])
      IO.puts("Writing #{model.name} to #{path}.")

      File.write!(
        path,
        Renderer.model(model, namespace)
      )
    end)

    token
  end

  defp load_global_optional_params(token) do
    params = token.rest_description.parameters || []

    global_optional_parameters =
      params
      |> Enum.map(fn {name, schema} ->
        Parameter.from_json_schema(name, schema, token.resource_context)
      end)
      |> Enum.sort_by(fn param -> param.name end)

    Map.put(token, :global_optional_parameters, global_optional_parameters)
  end

  defp load_apis(token) do
    Map.put(token, :apis, all_apis(token.rest_description, token.resource_context))
  end

  defp write_api_files(token) do
    token.apis
    |> Enum.each(fn api ->
      path = Path.join([token.base_dir, "api", api.filename])
      IO.puts("Writing #{api.name} to #{path}.")

      File.write!(
        path,
        Renderer.api(api, token.namespace, token.global_optional_parameters, token.data_wrapped)
      )
    end)

    token
  end

  @doc """
  Returns all Apis found from the provided RestDescription
  """
  @spec all_apis(RestDescription.t()) :: list(Api.t())
  def all_apis(rest_description) do
    all_apis(rest_description, ResourceContext.default())
  end

  @doc """
  Returns all Apis found from the provided RestDescription and ResourceContext
  """
  @spec all_apis(RestDescription.t(), ResourceContext.t()) :: list(Api.t())
  def all_apis(%{resources: resources}, context) do
    resources
    |> Enum.map(fn {name, resource} ->
      name = name |> String.replace("-", "_") |> Macro.camelize()
      methods = collect_methods(resource)

      %Api{
        name: name,
        description: "API calls for all endpoints tagged `#{name}`.",
        endpoints:
          Enum.flat_map(methods, fn {_, method} ->
            Endpoint.from_discovery_method(method, context)
          end)
      }
    end)
    |> Enum.sort_by(&(&1.name))
  end

  defp collect_methods(%{resources: resources, methods: methods}) do
    collect_methods_from_methods(methods) ++ collect_methods_from_resources(resources)
  end

  defp collect_methods_from_methods(nil), do: []
  defp collect_methods_from_methods(methods), do: Enum.into(methods, [])

  defp collect_methods_from_resources(nil), do: []

  defp collect_methods_from_resources(resources) do
    Enum.flat_map(resources, fn {_name, resource} ->
      collect_methods(resource)
    end)
  end

  @doc """
  Returns all Models found from the provided RestDescription
  """
  @spec all_models(RestDescription.t()) :: list(Model.t())
  def all_models(rest_description) do
    rest_description.schemas
    |> Model.from_schemas()
    |> Enum.sort_by(&(&1.name))
  end
end

# Copyright 2019 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

defmodule GoogleApis.Generator.ElixirGenerator do
  @moduledoc """
  Code generator written in Elixir which takes a Google Discovery document and
  generates an Elixir client library.
  """

  @behaviour GoogleApis.Generator
  alias GoogleApis.ApiConfig
  alias GoogleApi.Discovery.V1.Model.RestDescription

  alias GoogleApis.Generator.ElixirGenerator.{
    Api,
    Endpoint,
    Model,
    Parameter,
    Renderer,
    ResourceContext,
    Token
  }

  @doc """
  Run the generator for the specified api configuration
  """
  @spec generate_client(ApiConfig.t()) :: {:ok, any()} | {:error, String.t()}
  def generate_client(api_config) do
    Token.build(api_config)
    |> load_models
    |> set_model_filenames
    |> update_model_properties
    |> create_directories
    |> write_model_files
    |> load_global_optional_params
    |> load_apis
    |> set_api_filenames
    |> write_api_files
    |> write_connection
    |> write_mix_exs
    |> write_readme
    |> write_license
    |> write_gitignore
    |> write_config_exs
    |> write_test_helper_exs
  end

  defp load_models(token) do
    models = all_models(token.rest_description)

    token
    |> Map.put(:models, models)
    |> Map.put(
      :models_by_name,
      Enum.reduce(models, %{}, fn model, acc -> Map.put(acc, model.name, model) end)
    )
  end

  defp set_model_filenames(token) do
    {models, _} =
      token.models
      |> Enum.map_reduce(MapSet.new(), fn(model, used) ->
        {file, used} = find_unused_filename(Model.filename(model), used, 0)
        {%Model{model | filename: file}, used}
      end)
    %Token{token | models: models}
  end

  defp set_api_filenames(token) do
    {apis, _} =
      token.apis
      |> Enum.map_reduce(MapSet.new(), fn(api, used) ->
        {file, used} = find_unused_filename(Api.filename(api), used, 0)
        {%Api{api | filename: file}, used}
      end)
    %Token{token | apis: apis}
  end

  defp find_unused_filename(name, used, num) do
    candidate = filename_candidate(name, num)
    if MapSet.member?(used, candidate) do
      find_unused_filename(name, used, num + 1)
    else
      {candidate, MapSet.put(used, candidate)}
    end
  end

  def filename_candidate(name, 0), do: name

  def filename_candidate(name, num) do
    base = Path.basename(name, ".ex")
    "#{base}_#{num}.ex"
  end

  defp update_model_properties(token) do
    Map.update!(token, :models, fn models ->
      models
      |> Enum.map(fn model ->
        Model.update_properties(model, token.resource_context)
      end)
    end)
  end

  defp create_directories(token) do
    IO.puts("Creating leading directories")
    File.rm_rf!(token.base_dir)
    File.rm_rf!(Path.join(token.root_dir, ".swagger-codegen"))
    File.rm_rf!(Path.join(token.root_dir, ".swagger-codegen-ignore"))
    File.mkdir_p!(Path.join(token.base_dir, "api"))
    File.mkdir_p!(Path.join(token.base_dir, "model"))
    File.mkdir_p!(Path.join(token.root_dir, "config"))
    File.mkdir_p!(Path.join(token.root_dir, "test"))
    token
  end

  defp write_connection(token) do
    scopes = scopes_for(token.rest_description)
    otp_app = "google_api_#{token.library_name}"

    path = Path.join(token.base_dir, "connection.ex")
    IO.puts("Writing connection.ex.")

    File.write!(
      path,
      Renderer.connection(token.namespace, scopes, otp_app, token.base_url)
    )

    token
  end

  defp write_license(token) do
    path = Path.join(token.root_dir, "LICENSE")
    IO.puts("Writing LICENSE")
    File.write!(path, Renderer.license())

    token
  end

  defp write_config_exs(token) do
    path = Path.join(token.root_dir, "config/config.exs")
    IO.puts("Writing config/config.exs")
    has_env_config = token.root_dir |> Path.join("config/dev.exs") |> File.regular?()
    File.write!(path, Renderer.config_exs(has_env_config))

    token
  end

  defp write_test_helper_exs(token) do
    path = Path.join(token.root_dir, "test/test_helper.exs")
    IO.puts("Writing test/test_helper.exs")
    File.write!(path, Renderer.test_helper_exs(token.root_namespace))

    token
  end

  defp write_gitignore(token) do
    path = Path.join(token.root_dir, ".gitignore")
    IO.puts("Writing .gitignore")
    File.write!(path, Renderer.gitignore())

    token
  end

  defp write_mix_exs(token) do
    path = Path.join(token.root_dir, "mix.exs")
    IO.puts("Writing mix.exs")

    description = generate_hex_description(token.rest_description)
    docs_link = docs_link_for(token.rest_description)
    version = version_from_mix_exs(path)

    File.write!(
      path,
      Renderer.mix_exs(
        token.root_namespace,
        token.library_name,
        docs_link,
        version,
        description
      )
    )

    token
  end

  defp write_readme(token) do
    path = Path.join(token.root_dir, "README.md")
    IO.puts("Writing README.md")

    api_title = api_title_for(token.rest_description)
    docs_link = docs_link_for(token.rest_description)
    description = description_for(token.rest_description)
    version = token.root_dir |> Path.join("mix.exs") |> version_from_mix_exs() |> Version.parse!()
    version_requirement = "~> #{version.major}.#{version.minor}"
    library_name = "google_api_#{token.library_name}"

    File.write!(
      path,
      Renderer.readme(
        token.root_namespace,
        library_name,
        api_title,
        docs_link,
        version_requirement,
        description
      )
    )

    token
  end

  defp generate_hex_description(rest_info) do
    api_title = api_title_for(rest_info)
    description_start = "#{api_title} client library."
    rest_description = description_for(rest_info)

    if String.length(description_start) + String.length(rest_description) > 298 do
      description_start
    else
      "#{description_start} #{rest_description}"
    end
  end

  defp scopes_for(%{auth: nil}), do: []
  defp scopes_for(%{auth: %{oauth2: %{scopes: scopes}}}), do: scopes

  defp api_title_for(%{title: title}) when is_binary(title), do: title

  defp docs_link_for(%{documentationLink: link}) when is_binary(link), do: link
  defp docs_link_for(_), do: "https://cloud.google.com/"

  defp description_for(%{description: desc}) when is_binary(desc), do: desc
  defp description_for(_), do: ""

  defp version_from_mix_exs(path, default_version \\ "0.1.0") do
    with {:ok, old_content} <- File.read(path),
         [_, version] <- Regex.run(~r{@version "([\d\.]+)"}, old_content) do
      version
    else
      _ -> default_version
    end
  end

  defp write_model_files(%{models: models, namespace: namespace, base_dir: base_dir} = token) do
    models
    |> Enum.each(fn model ->
      path = Path.join([base_dir, "model", model.filename])
      IO.puts("Writing #{model.name} to #{path}.")

      File.write!(
        path,
        Renderer.model(model, namespace)
      )
    end)

    token
  end

  defp load_global_optional_params(token) do
    params = token.rest_description.parameters || []

    global_optional_parameters =
      params
      |> Enum.map(fn {name, schema} ->
        Parameter.from_json_schema(name, schema, token.resource_context)
      end)
      |> Enum.sort_by(fn param -> param.name end)

    Map.put(token, :global_optional_parameters, global_optional_parameters)
  end

  defp load_apis(token) do
    Map.put(token, :apis, all_apis(token.rest_description, token.resource_context))
  end

  defp write_api_files(token) do
    token.apis
    |> Enum.each(fn api ->
      path = Path.join([token.base_dir, "api", api.filename])
      IO.puts("Writing #{api.name} to #{path}.")

      File.write!(
        path,
        Renderer.api(api, token.namespace, token.global_optional_parameters, token.data_wrapped)
      )
    end)

    token
  end

  @doc """
  Returns all Apis found from the provided RestDescription
  """
  @spec all_apis(RestDescription.t()) :: list(Api.t())
  def all_apis(rest_description) do
    all_apis(rest_description, ResourceContext.default())
  end

  @doc """
  Returns all Apis found from the provided RestDescription and ResourceContext
  """
  @spec all_apis(RestDescription.t(), ResourceContext.t()) :: list(Api.t())
  def all_apis(%{resources: resources}, context) do
    resources
    |> Enum.map(fn {name, resource} ->
      name = name |> String.replace("-", "_") |> Macro.camelize()
      methods = collect_methods(resource)

      %Api{
        name: name,
        description: "API calls for all endpoints tagged `#{name}`.",
        endpoints:
          Enum.flat_map(methods, fn {_, method} ->
            Endpoint.from_discovery_method(method, context)
          end)
      }
    end)
    |> Enum.sort_by(&(&1.name))
  end

  defp collect_methods(%{resources: resources, methods: methods}) do
    collect_methods_from_methods(methods) ++ collect_methods_from_resources(resources)
  end

  defp collect_methods_from_methods(nil), do: []
  defp collect_methods_from_methods(methods), do: Enum.into(methods, [])

  defp collect_methods_from_resources(nil), do: []

  defp collect_methods_from_resources(resources) do
    Enum.flat_map(resources, fn {_name, resource} ->
      collect_methods(resource)
    end)
  end

  @doc """
  Returns all Models found from the provided RestDescription
  """
  @spec all_models(RestDescription.t()) :: list(Model.t())
  def all_models(rest_description) do
    rest_description.schemas
    |> Model.from_schemas()
    |> Enum.sort_by(&(&1.name))
  end
end