You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ch...@apache.org on 2018/06/22 22:15:30 UTC

[couchdb] 01/31: WIP: Elixir test suite

This is an automated email from the ASF dual-hosted git repository.

chewbranca pushed a commit to branch elixir-suite
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit d0993752a230ac29dc356fd0518d136a8a64c970
Author: Russell Branca <ch...@apache.org>
AuthorDate: Wed Nov 15 23:58:57 2017 +0000

    WIP: Elixir test suite
---
 elixir_suite/README.md            |  12 ++++
 elixir_suite/config/config.exs    |  30 ++++++++++
 elixir_suite/config/test.exs      |   3 +
 elixir_suite/lib/couch.ex         |  33 +++++++++++
 elixir_suite/mix.exs              |  30 ++++++++++
 elixir_suite/mix.lock             |   3 +
 elixir_suite/test/basics_test.exs | 118 ++++++++++++++++++++++++++++++++++++++
 elixir_suite/test/test_helper.exs |  73 +++++++++++++++++++++++
 8 files changed, 302 insertions(+)

diff --git a/elixir_suite/README.md b/elixir_suite/README.md
new file mode 100644
index 0000000..a7aedd3
--- /dev/null
+++ b/elixir_suite/README.md
@@ -0,0 +1,12 @@
+# Elixir Test Suite
+
+Proof of concept porting the JS test suite to Elixir.
+
+Currently the basics.js suite has been partially ported over.
+
+To run the suite:
+
+```
+mix deps.get
+mix test --trace
+```
diff --git a/elixir_suite/config/config.exs b/elixir_suite/config/config.exs
new file mode 100644
index 0000000..966ae83
--- /dev/null
+++ b/elixir_suite/config/config.exs
@@ -0,0 +1,30 @@
+# This file is responsible for configuring your application
+# and its dependencies with the aid of the Mix.Config module.
+use Mix.Config
+
+# This configuration is loaded before any dependency and is restricted
+# to this project. If another project depends on this project, this
+# file won't be loaded nor affect the parent project. For this reason,
+# if you want to provide default values for your application for
+# 3rd-party users, it should be done in your "mix.exs" file.
+
+# You can configure your application as:
+#
+#     config :foo, key: :value
+#
+# and access this configuration in your application as:
+#
+#     Application.get_env(:foo, :key)
+#
+# You can also configure a 3rd-party app:
+#
+#     config :logger, level: :info
+#
+
+# It is also possible to import configuration files, relative to this
+# directory. For example, you can emulate configuration per environment
+# by uncommenting the line below and defining dev.exs, test.exs and such.
+# Configuration from the imported file will override the ones defined
+# here (which is why it is important to import them last).
+#
+#     import_config "#{Mix.env}.exs"
diff --git a/elixir_suite/config/test.exs b/elixir_suite/config/test.exs
new file mode 100644
index 0000000..4b28ea9
--- /dev/null
+++ b/elixir_suite/config/test.exs
@@ -0,0 +1,3 @@
+config :logger,
+  backends: [:console],
+  compile_time_purge_level: :debug
diff --git a/elixir_suite/lib/couch.ex b/elixir_suite/lib/couch.ex
new file mode 100644
index 0000000..aafe829
--- /dev/null
+++ b/elixir_suite/lib/couch.ex
@@ -0,0 +1,33 @@
+defmodule Couch do
+  use HTTPotion.Base
+
+  @moduledoc """
+  CouchDB library to power test suite.
+  """
+
+  def process_url(url) do
+    "http://localhost:15984" <> url
+  end
+
+  def process_request_headers(headers) do
+    headers
+    |> Dict.put(:"User-Agent", "couch-potion")
+    |> Dict.put(:"Content-Type", "application/json")
+  end
+
+  def process_options(options) do
+    Dict.put options, :basic_auth, {"adm", "pass"}
+  end
+
+  def process_request_body(body) do
+    if is_map(body) do
+      :jiffy.encode(body)
+    else
+      body
+    end
+  end
+
+  def process_response_body(body) do
+    body |> IO.iodata_to_binary |> :jiffy.decode([:return_maps])
+  end
+end
diff --git a/elixir_suite/mix.exs b/elixir_suite/mix.exs
new file mode 100644
index 0000000..9b0f642
--- /dev/null
+++ b/elixir_suite/mix.exs
@@ -0,0 +1,30 @@
+defmodule Foo.Mixfile do
+  use Mix.Project
+
+  def project do
+    [
+      app: :foo,
+      version: "0.1.0",
+      elixir: "~> 1.5",
+      start_permanent: Mix.env == :prod,
+      deps: deps()
+    ]
+  end
+
+  # Run "mix help compile.app" to learn about applications.
+  def application do
+    [
+      extra_applications: [:logger]
+    ]
+  end
+
+  # Run "mix help deps" to learn about dependencies.
+  defp deps do
+    [
+      # {:dep_from_hexpm, "~> 0.3.0"},
+      {:httpotion, "~> 3.0"},
+      {:jiffy, "~> 0.14.11"}
+      # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
+    ]
+  end
+end
diff --git a/elixir_suite/mix.lock b/elixir_suite/mix.lock
new file mode 100644
index 0000000..0723e94
--- /dev/null
+++ b/elixir_suite/mix.lock
@@ -0,0 +1,3 @@
+%{"httpotion": {:hex, :httpotion, "3.0.3", "17096ea1a7c0b2df74509e9c15a82b670d66fc4d66e6ef584189f63a9759428d", [], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"},
+  "ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [], [], "hexpm"},
+  "jiffy": {:hex, :jiffy, "0.14.11", "919a87d491c5a6b5e3bbc27fafedc3a0761ca0b4c405394f121f582fd4e3f0e5", [], [], "hexpm"}}
diff --git a/elixir_suite/test/basics_test.exs b/elixir_suite/test/basics_test.exs
new file mode 100644
index 0000000..87b4aff
--- /dev/null
+++ b/elixir_suite/test/basics_test.exs
@@ -0,0 +1,118 @@
+defmodule BasicsTest do
+  use CouchTestCase
+
+  @moduledoc """
+  Test CouchDB basics.
+  This is a port of the basics.js suite
+  """
+
+  test "Session contains adm context" do
+    userCtx = Couch.get("/_session").body["userCtx"]
+    assert userCtx["name"] == "adm", "Should have adm user context"
+    assert userCtx["roles"] == ["_admin"], "Should have _admin role"
+  end
+
+  test "Welcome endpoint" do
+    assert Couch.get("/").body["couchdb"] == "Welcome", "Should say welcome"
+  end
+
+  @tag :with_db
+  test "PUT on existing DB should return 412 instead of 500", context do
+    db_name = context[:db_name]
+    assert Couch.put("/#{db_name}").status_code == 412
+  end
+
+  @tag :with_db_name
+  test "Creating a new DB should return location header", context do
+    db_name = context[:db_name]
+    {:ok, resp} = create_db(db_name)
+    msg = "Should return Location header for new db"
+    assert String.ends_with?(resp.headers["location"], db_name), msg
+    {:ok, _} = delete_db(db_name)
+  end
+
+  @tag :with_db_name
+  test "Creating a new DB with slashes should return Location header (COUCHDB-411)", context do
+    db_name = context[:db_name] <> "%2Fwith_slashes"
+    {:ok, resp} = create_db(db_name)
+    msg = "Should return Location header for new db"
+    assert String.ends_with?(resp.headers["location"], db_name), msg
+    {:ok, _} = delete_db(db_name)
+  end
+
+  @tag :with_db
+  test "Created database has appropriate db info name", context do
+    db_name = context[:db_name]
+    assert Couch.get("/#{db_name}").body["db_name"] == db_name, "Get correct database name"
+  end
+
+  @tag :with_db
+  test "Database should be in _all_dbs", context do
+    assert context[:db_name] in Couch.get("/_all_dbs").body, "Db name in _all_dbs"
+  end
+
+  @tag :with_db
+  test "Empty database should have zero docs", context do
+    assert Couch.get("/#{context[:db_name]}").body["doc_count"] == 0, "Empty doc count in empty db"
+  end
+
+  @tag :with_db
+  test "Create a document and save it to the database", context do
+    resp = Couch.post("/#{context[:db_name]}", [body: %{:_id => "0", :a => 1, :b => 1}])
+    assert resp.status_code == 201, "Should be 201 created"
+    assert resp.body["id"], "Id should be present"
+    assert resp.body["rev"], "Rev should be present"
+
+    resp2 = Couch.get("/#{context[:db_name]}/#{resp.body["id"]}")
+    assert resp2.body["_id"] == resp.body["id"], "Ids should match"
+    assert resp2.body["_rev"] == resp.body["rev"], "Revs should match"
+  end
+
+  @tag :with_db
+  test "Revs info status is good", context do
+    db_name = context[:db_name]
+    {:ok, _} = create_doc(db_name, sample_doc_foo())
+    resp = Couch.get("/#{db_name}/foo", [query: %{:revs_info => true}])
+    assert hd(resp.body["_revs_info"])["status"] == "available", "Revs info is available"
+  end
+
+  @tag :with_db
+  test "Make sure you can do a seq=true option", context do
+    db_name = context[:db_name]
+    {:ok, _} = create_doc(db_name, sample_doc_foo())
+    resp = Couch.get("/#{db_name}/foo", [query: %{:local_seq => true}])
+    assert resp.body["_local_seq"] == 1, "Local seq value == 1"
+  end
+
+  @tag :with_db
+  test "Can create several documents", context do
+    db_name = context[:db_name]
+    assert Couch.post("/#{db_name}", [body: %{:_id => "1", :a => 2, :b => 4}]).body["ok"]
+    assert Couch.post("/#{db_name}", [body: %{:_id => "2", :a => 3, :b => 9}])
+    assert Couch.post("/#{db_name}", [body: %{:_id => "3", :a => 4, :b => 16}]).body["ok"]
+    assert Couch.get("/#{db_name}").body["doc_count"] == 3
+  end
+
+  @tag :with_db
+  test "Regression test for COUCHDB-954", context do
+    db_name = context[:db_name]
+    doc = %{:_id => "COUCHDB-954", :a => 1}
+
+    resp1 = Couch.post("/#{db_name}", [body: doc])
+    assert resp1.body["ok"]
+    old_rev = resp1.body["rev"]
+
+    doc = Map.put(doc, :_rev, old_rev)
+    resp2 = Couch.post("/#{db_name}", [body: doc])
+    assert resp2.body["ok"]
+    new_rev = resp2.body["rev"]
+
+    # TODO: enable chunked encoding
+    #resp3 = Couch.get("/#{db_name}/COUCHDB-954", [query: %{:open_revs => "[#{old_rev}, #{new_rev}]"}])
+    #assert length(resp3.body) == 2, "Should get two revisions back"
+    #resp3 = Couch.get("/#{db_name}/COUCHDB-954", [query: %{:open_revs => "[#{old_rev}]", :latest => true}])
+    #assert resp3.body["_rev"] == new_rev
+  end
+
+
+end
diff --git a/elixir_suite/test/test_helper.exs b/elixir_suite/test/test_helper.exs
new file mode 100644
index 0000000..181642b
--- /dev/null
+++ b/elixir_suite/test/test_helper.exs
@@ -0,0 +1,73 @@
+ExUnit.start()
+
+# TODO
+#def random_db_name do
+#  "asdf"
+#end
+
+defmodule CouchTestCase do
+  use ExUnit.Case
+
+  defmacro __using__(_opts) do
+    quote do
+      require Logger
+      use ExUnit.Case
+
+      setup context do
+        db_name = if context[:with_db] != nil or context[:with_db_name] != nil do
+          if context[:with_db] != nil and context[:with_db] != true do
+            context[:with_db]
+          else
+            case context[:with_db_name] do
+              nil -> random_db_name()
+              true -> random_db_name()
+              name -> name
+            end
+          end
+        end
+
+        if context[:with_db] != nil do
+          {:ok, _} = create_db(db_name)
+
+          on_exit(fn -> delete_db(db_name) end)
+        end
+
+        {:ok, db_name: db_name}
+      end
+
+      def random_db_name do
+        time = :erlang.monotonic_time()
+        umi = :erlang.unique_integer([:monotonic])
+        "random-test-db-#{time}-#{umi}"
+      end
+
+      def create_db(db_name) do
+        resp = Couch.put("/#{db_name}")
+        assert resp.status_code == 201
+        assert resp.body == %{"ok" => true}
+        {:ok, resp}
+      end
+
+      def delete_db(db_name) do
+        resp = Couch.delete("/#{db_name}")
+        assert resp.status_code == 200
+        assert resp.body == %{"ok" => true}
+        {:ok, resp}
+      end
+
+      def create_doc(db_name, body) do
+        resp = Couch.post("/#{db_name}", [body: body])
+        assert resp.status_code == 201
+        assert resp.body["ok"]
+        {:ok, resp}
+      end
+
+      def sample_doc_foo do
+        %{
+          "_id": "foo",
+          "bar": "baz"
+        }
+      end
+    end
+  end
+end