blob: e08229f8238dc0d9bfd12749d8d5f670bd772ddf [file] [log] [blame]
ExUnit.configure(exclude: [pending: true])
ExUnit.start()
defmodule CouchTestCase do
use ExUnit.Case
defmacro __using__(_opts) do
quote do
require Logger
use ExUnit.Case
setup context do
setup_funs = [
&set_db_context/1,
&set_config_context/1,
&set_user_context/1
]
context =
Enum.reduce(setup_funs, context, fn setup_fun, acc ->
setup_fun.(acc)
end)
{:ok, context}
end
def set_db_context(context) do
context =
case context do
%{:with_db_name => true} ->
Map.put(context, :db_name, random_db_name())
%{:with_db_name => db_name} when is_binary(db_name) ->
Map.put(context, :db_name, db_name)
%{:with_random_db => db_name} when is_binary(db_name) ->
context
|> Map.put(:db_name, random_db_name(db_name))
|> Map.put(:with_db, true)
%{:with_db => true} ->
Map.put(context, :db_name, random_db_name())
%{:with_db => db_name} when is_binary(db_name) ->
Map.put(context, :db_name, db_name)
_ ->
context
end
if Map.has_key?(context, :with_db) do
{:ok, _} = create_db(context[:db_name])
on_exit(fn -> delete_db(context[:db_name]) end)
end
context
end
def set_config_context(context) do
if is_list(context[:config]) do
Enum.each(context[:config], fn cfg ->
set_config(cfg)
end)
end
context
end
def set_user_context(context) do
case Map.get(context, :user) do
nil ->
context
user when is_list(user) ->
user = create_user(user)
on_exit(fn ->
query = %{:rev => user["_rev"]}
resp = Couch.delete("/_users/#{user["_id"]}", query: query)
assert HTTPotion.Response.success?(resp)
end)
context = Map.put(context, :user, user)
userinfo = user["name"] <> ":" <> user["password"]
Map.put(context, :userinfo, userinfo)
end
end
def random_db_name do
random_db_name("random-test-db")
end
def random_db_name(prefix) do
time = :erlang.monotonic_time()
umi = :erlang.unique_integer([:monotonic])
"#{prefix}-#{time}-#{umi}"
end
def set_config({section, key, value}) do
existing = set_config_raw(section, key, value)
on_exit(fn ->
Enum.each(existing, fn {node, prev_value} ->
if prev_value != "" do
url = "/_node/#{node}/_config/#{section}/#{key}"
headers = ["X-Couch-Persist": "false"]
body = :jiffy.encode(prev_value)
resp = Couch.put(url, headers: headers, body: body)
assert resp.status_code == 200
else
url = "/_node/#{node}/_config/#{section}/#{key}"
headers = ["X-Couch-Persist": "false"]
resp = Couch.delete(url, headers: headers)
assert resp.status_code == 200
end
end)
end)
end
def set_config_raw(section, key, value) do
resp = Couch.get("/_membership")
Enum.map(resp.body["all_nodes"], fn node ->
url = "/_node/#{node}/_config/#{section}/#{key}"
headers = ["X-Couch-Persist": "false"]
body = :jiffy.encode(value)
resp = Couch.put(url, headers: headers, body: body)
assert resp.status_code == 200
{node, resp.body}
end)
end
def create_user(user) do
required = [:name, :password, :roles]
Enum.each(required, fn key ->
assert Keyword.has_key?(user, key), "User missing key: #{key}"
end)
name = Keyword.get(user, :name)
password = Keyword.get(user, :password)
roles = Keyword.get(user, :roles)
assert is_binary(name), "User name must be a string"
assert is_binary(password), "User password must be a string"
assert is_list(roles), "Roles must be a list of strings"
Enum.each(roles, fn role ->
assert is_binary(role), "Roles must be a list of strings"
end)
user_doc = %{
"_id" => "org.couchdb.user:" <> name,
"type" => "user",
"name" => name,
"roles" => roles,
"password" => password
}
resp = Couch.get("/_users/#{user_doc["_id"]}")
user_doc =
case resp.status_code do
404 ->
user_doc
sc when sc >= 200 and sc < 300 ->
Map.put(user_doc, "_rev", resp.body["_rev"])
end
resp = Couch.post("/_users", body: user_doc)
assert HTTPotion.Response.success?(resp)
assert resp.body["ok"]
Map.put(user_doc, "_rev", resp.body["rev"])
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
# Generate range of docs with strings as keys
def make_docs(id_range) do
for id <- id_range, str_id = Integer.to_string(id) do
%{"_id" => str_id, "integer" => id, "string" => str_id}
end
end
# Generate range of docs with atoms as keys, which are more
# idiomatic, and are encoded by jiffy to binaries
def create_docs(id_range) do
for id <- id_range, str_id = Integer.to_string(id) do
%{_id: str_id, integer: id, string: str_id}
end
end
def retry_until(condition, sleep \\ 100, timeout \\ 5000) do
retry_until(condition, now(:ms), sleep, timeout)
end
defp retry_until(condition, start, sleep, timeout) do
now = now(:ms)
if now > start + timeout do
raise "timed out after #{now - start} ms"
else
try do
if condition.() do
:ok
else
raise ExUnit.AssertionError
end
rescue
ExUnit.AssertionError ->
:timer.sleep(sleep)
retry_until(condition, start, sleep, timeout)
end
end
end
defp now(:ms) do
div(:erlang.system_time(), 1_000_000)
end
@spec rev(map(), map()) :: map()
def rev(doc = %{_id: id}, %{"id" => id, "rev" => rev}) do
Map.put(doc, :_rev, rev)
end
@spec rev([map()], [map()]) :: [map()]
def rev(docs, rows) when length(docs) == length(rows) do
for {doc, row} <- Enum.zip(docs, rows), do: rev(doc, row)
end
def pretty_inspect(resp) do
opts = [pretty: true, width: 20, limit: :infinity, printable_limit: :infinity]
inspect(resp, opts)
end
end
end
end