diff --git a/Makefile b/Makefile index 6831f890..d4bc9f98 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,14 @@ endif DOCKER_IMAGE ?= operable/cog:0.5-dev +ifeq ($(wildcard NO_CI),) ci: export DATABASE_URL = $(TEST_DATABASE_URL) ci: export MIX_ENV = test ci: ci-setup test-all ci-cleanup ci-reset: @echo "Resetting build environment" - @rm -rf _build +# @rm -rf _build ci-setup: ci-reset # Nuke mnesia dirs so we don't get borked on emqttd upgrades @@ -27,6 +28,11 @@ ci-setup: ci-reset ci-cleanup: mix ecto.drop +else +ci: + @echo "NO_CI file found. CI build targets skipped." + @exit 0 +endif setup: mix deps.get @@ -51,8 +57,13 @@ test: reset-db mix test $(TEST) test-all: export MIX_ENV = test -test-all: reset-db - mix test +test-all: unit-tests integration-tests + +unit-tests: reset-db + mix test --exclude=integration + +integration-tests: reset-db + mix test --only=integration test-watch: export MIX_ENV = test test-watch: reset-db @@ -66,4 +77,4 @@ coverage: docker: docker build --build-arg MIX_ENV=prod -t $(DOCKER_IMAGE) . -.PHONY: ci ci-setup ci-cleanup test docker +.PHONY: ci ci-setup ci-cleanup test docker unit-tests integration-tests diff --git a/config/config.exs b/config/config.exs index 47ef4d1f..4d079d40 100644 --- a/config/config.exs +++ b/config/config.exs @@ -22,13 +22,6 @@ config :cog, :embedded_bundle_version, "0.13.0" # Chat Adapters # ======================================================================== -config :cog, Cog.Chat.Adapter, - providers: [http: Cog.Chat.Http.Provider], - cache_ttl: {60, :sec} - -config :cog, Cog.Chat.Slack.Provider, - api_token: System.get_env("SLACK_API_TOKEN") - config :cog, Cog.Chat.Http.Provider, foo: "blah" @@ -193,4 +186,7 @@ config :cog, Cog.Mailer, config :cog, :email_from, System.get_env("COG_EMAIL_FROM") config :cog, :password_reset_base_url, System.get_env("COG_PASSWORD_RESET_BASE_URL") +import_config "slack.exs" +import_config "hipchat.exs" + import_config "#{Mix.env}.exs" diff --git a/config/dev.exs b/config/dev.exs index fb7981e1..a54be972 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -9,11 +9,10 @@ config :lager, :handlers, config :cog, :enable_spoken_commands, ensure_boolean(System.get_env("ENABLE_SPOKEN_COMMANDS")) || false -config :cog, - :template_cache_ttl, {1, :sec} - config :cog, Cog.Chat.Adapter, - providers: [slack: Cog.Chat.Slack.Provider], + providers: [slack: Cog.Chat.Slack.Provider, + http: Cog.Chat.Http.Provider], + cache_ttl: {1, :sec}, chat: :slack config :cog, Cog.Endpoint, diff --git a/config/hipchat.exs b/config/hipchat.exs new file mode 100644 index 00000000..da7a9803 --- /dev/null +++ b/config/hipchat.exs @@ -0,0 +1,10 @@ +use Mix.Config + +config :cog, Cog.Chat.HipChat.Provider, + api_root: System.get_env("HIPCHAT_API_ROOT") || "https://api.hipchat.com/v2", + chat_host: System.get_env("HIPCHAT_CHAT_HOST") || "chat.hipchat.com", + conf_host: System.get_env("HIPCHAT_CONF_HOST") || "conf.hipchat.com", + api_token: System.get_env("HIPCHAT_API_TOKEN"), + nickname: System.get_env("HIPCHAT_NICKNAME"), + jabber_id: System.get_env("HIPCHAT_JABBER_ID"), + jabber_password: System.get_env("HIPCHAT_JABBER_PASSWORD") diff --git a/config/prod.exs b/config/prod.exs index f017166b..64e54b1c 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -11,4 +11,5 @@ config :comeonin, config :cog, Cog.Chat.Adapter, providers: [slack: Cog.Chat.Slack.Provider], - chat: :slack + chat: :slack, + cache_ttl: {60, sec} diff --git a/config/slack.exs b/config/slack.exs new file mode 100644 index 00000000..87167f39 --- /dev/null +++ b/config/slack.exs @@ -0,0 +1,4 @@ +use Mix.Config + +config :cog, Cog.Chat.Slack.Provider, + api_token: System.get_env("SLACK_API_TOKEN") diff --git a/config/test.exs b/config/test.exs index 3e407d7d..b391fdc8 100644 --- a/config/test.exs +++ b/config/test.exs @@ -7,7 +7,9 @@ config :lager, :handlers, [{LagerLogger, [level: :error]}] config :cog, Cog.Chat.Adapter, - providers: [test: Cog.Chat.Test.Provider], + providers: [test: Cog.Chat.Test.Provider, + http: Cog.Chat.Http.Provider], + cache_ttl: {1, :sec}, chat: :test config :cog, Cog.Chat.Test.Provider, diff --git a/lib/cog.ex b/lib/cog.ex index a9559dee..0c26203f 100644 --- a/lib/cog.ex +++ b/lib/cog.ex @@ -4,6 +4,14 @@ defmodule Cog do import Supervisor.Spec, warn: false + def restart() do + Logger.debug("Stopping Cog...") + :ok = Application.stop(:cog) + Logger.debug("Cog stopped. Restarting...") + {:ok, _} = Application.ensure_all_started(:cog) + Logger.debug("Cog restarted.") + end + def start(_type, _args) do maybe_display_unenforcing_warning() children = [worker(Cog.BusDriver, [], shutdown: 1000), diff --git a/lib/cog/chat/adapter.ex b/lib/cog/chat/adapter.ex index b7158d41..b93e0a21 100644 --- a/lib/cog/chat/adapter.ex +++ b/lib/cog/chat/adapter.ex @@ -246,8 +246,8 @@ defmodule Cog.Chat.Adapter do false -> state end - _error -> - Logger.error("Ignoring invalid chat message: #{inspect message, pretty: true}") + error -> + Logger.error("Error decoding chat message: #{inspect error} #{inspect message, pretty: true}") state end {:noreply, state} @@ -332,14 +332,14 @@ defmodule Cog.Chat.Adapter do defp parse_mention(_text, nil), do: nil defp parse_mention(text, bot_name) do - updated = Regex.replace(~r/^#{Regex.escape(bot_name)}/, text, "") + updated = Regex.replace(~r/^#{Regex.escape(bot_name)}/i, text, "") if updated != text do Regex.replace(~r/^:/, updated, "") |> String.trim - else - nil - end - end + else + nil + end + end defp prepare_target(target) do case Cog.Chat.Room.from_map(target) do diff --git a/lib/cog/chat/hipchat/connector.ex b/lib/cog/chat/hipchat/connector.ex new file mode 100644 index 00000000..9ef7a90b --- /dev/null +++ b/lib/cog/chat/hipchat/connector.ex @@ -0,0 +1,389 @@ +defmodule Cog.Chat.HipChat.Connector do + + require Logger + + use GenServer + + alias Cog.Chat.HipChat.Users + alias Cog.Chat.HipChat.Provider + alias Cog.Chat.HipChat.Rooms + alias Cog.Chat.HipChat.TemplateProcessor + alias Cog.Chat.HipChat.Util + alias Cog.Repository.ChatProviders + alias Romeo.Connection + alias Romeo.Stanza + + + @provider_name "hipchat" + @xmpp_timeout 5000 + @room_refresh_interval 5000 + @heartbeat_interval 30000 + + defstruct [:provider, :xmpp_conn, :hipchat_org, :api_token, :api_root, :conf_host, :me, :mention_name, + :xmpp_name, :users, :rooms] + + def start_link(config) do + GenServer.start_link(__MODULE__, [config, self()], name: __MODULE__) + end + + def init([config, provider]) do + api_root = Keyword.fetch!(config, :api_root) + chat_host = Keyword.fetch!(config, :chat_host) + conf_host = Keyword.fetch!(config, :conf_host) + api_token = Keyword.fetch!(config, :api_token) + jabber_id = Keyword.fetch!(config, :jabber_id) + jabber_password = Keyword.fetch!(config, :jabber_password) + nickname = Keyword.fetch!(config, :nickname) + use_ssl = Keyword.get(config, :ssl, true) + [user_name|_] = String.split(jabber_id, "@", parts: 2) + [hipchat_org|_] = String.split(user_name, "_", parts: 2) + opts = [host: chat_host, + jid: jabber_id, + password: jabber_password, + require_tls: use_ssl] + case Connection.start_link(opts) do + {:ok, conn} -> + users = %Users{} + case Users.lookup(users, conn, jid: jabber_id) do + {nil, _users} -> + {:error, "Cannot find HipChat user associated with bot JID #{jabber_id}"} + {user, users} -> + state = %__MODULE__{xmpp_conn: conn, hipchat_org: hipchat_org, users: users, + rooms: Rooms.new(api_root, api_token), me: Keyword.fetch!(opts, :jid), provider: provider, + api_token: api_token, mention_name: nickname, xmpp_name: user.mention_name, + api_root: api_root, conf_host: conf_host} + case Connection.send(conn, Stanza.presence) do + :ok -> + Logger.info("Successfully connected to HipChat organization #{hipchat_org} as '#{state.mention_name}'") + {:ok, state} + error -> + error + end + error -> + error + end + end + end + + def handle_call({:lookup_user_jid, jid}, _from, state) do + {result, state} = lookup_user(state, jid: jid) + case result do + {:ok, nil} -> + {:reply, {:error, :not_found}, state} + {:ok, user} -> + {:reply, {:ok, user}, state} + _ -> + {:reply, {:error, :lookup_failed}, state} + end + end + def handle_call({:lookup_user_handle, handle}, _from, state) do + {result, state} = lookup_user(state, handle: handle) + case result do + {:ok, nil} -> + {:reply, {:error, :not_found}, state} + {:ok, user} -> + {:reply, {:ok, user}, state} + _ -> + {:reply, {:error, :lookup_failed}, state} + end + end + def handle_call({:lookup_room_jid, room_jid}, _from, state) do + {result, state} = lookup_room(state, jid: room_jid) + case result do + {:ok, nil} -> + # This might be an attempt to resolve the 'me' redirect target + # so let's try resolving the room jid as a user + {result, state} = lookup_user(state, jid: room_jid) + case result do + {:ok, nil} -> + {:reply, {:error, :not_found}, state} + # We found the user so let's construct a DM "room" + # usable as a redirect + {:ok, user} -> + room = %Cog.Chat.Room{id: user.id, + is_dm: true, + name: "direct", + provider: @provider_name} + {:reply, {:ok, room}, state} + _ -> + {:reply, {:error, :lookup_failed}, state} + end + {:ok, room} -> + {:reply, {:ok, room}, state} + _ -> + {:reply, {:error, :lookup_failed}, state} + end + end + def handle_call({:lookup_room_name, room_name}, _from, state) do + {result, state} = lookup_room(state, name: room_name) + case result do + {:ok, nil} -> + {:reply, {:error, :not_found}, state} + {:ok, room} -> + {:reply, {:ok, room}, state} + _ -> + {:reply, {:error, :lookup_failed}, state} + end + end + def handle_call(:list_joined_rooms, _from, state) do + {:reply, {:ok, Rooms.all(state.rooms)}, state} + end + def handle_call({:send_message, target, message}, _from, state) do + send_output(state, target, TemplateProcessor.render(message)) + end + + # Should only happen when we connect + def handle_info(:connection_ready, state) do + case rejoin_rooms(state) do + {:ok, state} -> + :timer.send_interval(@heartbeat_interval, :heartbeat) + {:noreply, state} + _error -> + {:stop, :init_error, state} + end + end + # Send heartbeat + def handle_info(:heartbeat, state) do + case Connection.send(state.xmpp_conn, Stanza.chat(state.me, " ")) do + :ok -> + :ok + error -> + Logger.error("Failed to send heartbeat message to HipChat: #{inspect error}") + end + {:noreply, state} + end + def handle_info({:stanza, %Stanza.Presence{}=presence}, state) do + {:ok, state} = handle_presence(state, presence) + {:noreply, state} + end + def handle_info({:stanza, %Stanza.Message{}=message}, state) do + state = case Util.classify_message(message) do + {:invite, room_name} -> + Logger.info("Received an invite to MUC room '#{room_name}'") + join_room(room_name, state) + state + {:groupchat, room_jid, sender, body} -> + handle_groupchat(room_jid, sender, body, state) + {:dm, sender_jid, body} -> + case Users.lookup(state.users, state.xmpp_conn, jid: sender_jid) do + {nil, users} -> + %{state | users: users} + {user, users} -> + state = %{state | users: users} + handle_dm(user.mention_name, body, state) + end + :ignore -> + state + end + {:noreply, state} + end + def handle_info(_msg, state) do + {:noreply, state} + end + + def terminate(_, _, _), do: :ok + + defp handle_presence(state, presence) do + case lookup_user(state, handle: presence.from.user) do + {{:ok, nil}, state} -> + {:ok, state} + {{:ok, user}, state} -> + GenServer.cast(Provider, build_event(user, presence)) + {:ok, state} + _ -> + {:ok, state} + end + end + + defp build_event(user, %Stanza.Presence{}=presence) do + {:chat_event, %{"presence" => presence_type(presence.show), "provider" => @provider_name, "type" => "presence_change", + "user" => user}} + end + + defp presence_type("xa"), do: "inactive" + defp presence_type("away"), do: "inactive" + defp presence_type("chat"), do: "active" + defp presence_type(""), do: "active" + + defp lookup_user(state, try_both: name_or_jid) do + case lookup_user(state, jid: name_or_jid) do + {{:ok, nil}, state} -> + lookup_user(state, handle: name_or_jid) + results -> + results + end + end + defp lookup_user(state, opts) do + case Users.lookup(state.users, state.xmpp_conn, opts) do + {:error, _} -> + {{:error, :lookup_failed}, state} + {result, users} -> + {{:ok, result}, %{state | users: users}} + end + end + + defp lookup_room(state, opts) do + case Rooms.lookup(state.rooms, state.xmpp_conn, opts) do + {:error, _} -> + {{:error, :lookup_failed}, state} + {result, rooms} -> + {{:ok, result}, %{state | rooms: rooms}} + end + end + + defp handle_groupchat(room_jid, sender, body, state) do + if sender != state.me and sender != state.mention_name do + case lookup_room(state, jid: room_jid) do + {{:ok, nil}, state} -> + state + {{:ok, room}, state} -> + case lookup_user(state, jid: sender) do + {{:ok, nil}, state} -> + Logger.debug("Roster miss for #{inspect sender}") + state + {{:ok, user}, state} -> + GenServer.cast(state.provider, {:chat_message, %Cog.Chat.Message{id: Cog.Events.Util.unique_id, + room: room, user: user, text: body, provider: @provider_name, + bot_name: "@#{state.mention_name}", edited: false}}) + state + error -> + Logger.error("Failed to lookup sender of groupchat message: #{inspect error}") + state + end + error -> + Logger.error("Failed to lookup room for groupchat message: #{inspect error}") + end + else + state + end + end + + defp handle_dm(sender, body, state) do + case lookup_user(state, jid: sender) do + {{:ok, nil}, state} -> + state + {{:ok, user}, state} -> + room = %Cog.Chat.Room{id: sender, + is_dm: true, + name: "direct", + provider: @provider_name} + GenServer.cast(state.provider, {:chat_message, %Cog.Chat.Message{id: Cog.Events.Util.unique_id, + room: room, user: user, text: body, provider: @provider_name, + bot_name: "@#{state.mention_name}", edited: false}}) + state + end + end + + defp send_output(state, target, output) do + case lookup_user(state, try_both: target) do + {{:ok, nil}, state} -> + case lookup_room(state, name: target) do + {{:ok, nil}, state} -> + Logger.warn("Unknown message target '#{target}'. Message NOT sent.") + {:reply, :ok, state} + {{:ok, room}, state} -> + send_room_message(room, output, state) + error -> + Logger.error("Failed to lookup target '#{target}' as room. Message NOT sent: #{inspect error}") + end + {{:ok, user}, state} -> + send_user_message(user, output, state) + error -> + Logger.error("Failed to lookup target '#{target}' as user. Message NOT sent: #{inspect error}") + end + end + + defp prepare_message(text) when is_binary(text) do + Poison.encode!(%{message_format: "html", + color: "gray", + notify: false, + message: text}) + end + + defp send_room_message(room, message, state) do + body = prepare_message(message) + url = Enum.join(["room", room.secondary_id, "notification"], "/") + response = call_api(state, url, body) + unless HTTPotion.Response.success?(response) do + Logger.error("Sending message to room '#{room.name}' failed: #{response.body}") + end + {:reply, :ok, state} + end + + defp send_user_message(user, message, state) do + body = prepare_message(message) + url = Enum.join(["user", user.email, "message"], "/") + response = call_api(state, url, body) + unless HTTPotion.Response.success?(response) do + Logger.error("Sending message to user '#{user.handle}' failed: #{response.body}") + end + {:reply, :ok, state} + end + + defp rejoin_rooms(state) do + case ChatProviders.get_provider_state(@provider_name) do + nil -> + {:ok, state} + pstate -> + rooms = finalize_rooms(Map.get(pstate, "rooms", []), System.get_env("HIPCHAT_ROOMS")) + unless length(rooms) == 0 do + Logger.info("Rejoining #{Enum.join(rooms, ", ")}") + end + case Enum.reduce(rooms, [], + fn(room_name, acc) -> + case join_room(room_name, state) do + :ok -> + acc + :error -> + [room_name|acc] + end end) do + [] -> + {:ok, state} + errors -> + Logger.error("Failed to rejoin the following rooms: #{Enum.join(errors, ",")}") + {:ok, state} + end + end + end + + defp finalize_rooms(pstate_rooms, nil), do: pstate_rooms + defp finalize_rooms(pstate_rooms, envvar_rooms) do + env_rooms = String.split(envvar_rooms, " ", trim: true) + Enum.uniq(pstate_rooms ++ env_rooms) + end + + defp join_room(room_name, state) do + # If we were given a JID then use it, else build a JID + # using the hipchat org and conf_host + muc_name = if String.contains?(room_name, state.conf_host) do + room_name + else + "#{state.hipchat_org}_#{room_name}@#{state.conf_host}" + end + case Connection.send(state.xmpp_conn, Stanza.join(muc_name, state.xmpp_name)) do + :ok -> + Logger.info("Successfully joined MUC room '#{room_name}'") + pstate = ChatProviders.get_provider_state(@provider_name) + updated = Map.update(pstate, "rooms", [room_name], &(Enum.uniq([room_name|&1]))) + case ChatProviders.set_provider_state(@provider_name, updated) do + {:ok, _} -> + :ok + error -> + Logger.error("Failed to save joined room to persistent state: #{inspect error}") + :ok + end + error -> + Logger.error("Failed to join MUC room '#{room_name}': #{inspect error}") + :error + end + end + + defp call_api(state, url, body) do + url = Enum.join([state.api_root, url], "/") + HTTPotion.post(url, headers: ["Content-Type": "application/json", + "Accepts": "application/json", + "Authorization": "Bearer #{state.api_token}"], + body: body) + end + +end diff --git a/lib/cog/chat/hipchat/provider.ex b/lib/cog/chat/hipchat/provider.ex new file mode 100644 index 00000000..6ab86626 --- /dev/null +++ b/lib/cog/chat/hipchat/provider.ex @@ -0,0 +1,87 @@ +defmodule Cog.Chat.HipChat.Provider do + + require Logger + + use GenServer + use Cog.Chat.Provider + + alias Carrier.Messaging.Connection + alias Carrier.Messaging.GenMqtt + alias Cog.Chat.HipChat + + defstruct [:token, :jabber_id, :jabber_password, :nickname, :mbus, :xmpp, :incoming] + + def display_name, do: "HipChat" + + def lookup_user(handle) do + if String.match?(handle, ~r/.+@.+/) do + GenServer.call(__MODULE__, {:call_connector, {:lookup_user_jid, handle}}, :infinity) + else + GenServer.call(__MODULE__, {:call_connector, {:lookup_user_handle, handle}}, :infinity) + end + end + + def lookup_room({:name, name}) do + case GenServer.call(__MODULE__, {:call_connector, {:lookup_room_name, name}}, :infinity) do + {:error, :not_found} -> + # This might be a redirect so let's try looking up the "room" + # as a user + case lookup_user(name) do + {:ok, user} -> + {:ok, %Room{id: user.id, + is_dm: true, + provider: "hipchat", + name: "direct"}} + error -> + error + end + result -> + result + end + end + def lookup_room({:id, id}) do + GenServer.call(__MODULE__, {:call_connector, {:lookup_room_jid, id}}, :infinity) + end + + def list_joined_rooms() do + GenServer.call(__MODULE__, {:call_connector, :list_joined_rooms}, :infinity) + end + + def send_message(target, message) do + GenServer.call(__MODULE__, {:call_connector, {:send_message, target, message}}, :infinity) + end + + def start_link(config) do + case Application.ensure_all_started(:romeo) do + {:ok, _} -> + GenServer.start_link(__MODULE__, [config], name: __MODULE__) + error -> + error + end + end + + def init([config]) do + incoming = Keyword.fetch!(config, :incoming_topic) + case HipChat.Connector.start_link(config) do + {:ok, xmpp_conn} -> + {:ok, mbus} = Connection.connect() + {:ok, %__MODULE__{incoming: incoming, mbus: mbus, xmpp: xmpp_conn}} + error -> + error + end + end + + def handle_call({:call_connector, connector_message}, _from, state) do + {:reply, GenServer.call(state.xmpp, connector_message, :infinity), state} + end + + def handle_cast({:chat_event, event}, state) do + GenMqtt.cast(state.mbus, state.incoming, "event", event) + {:noreply, state} + end + def handle_cast({:chat_message, msg}, state) do + GenMqtt.cast(state.mbus, state.incoming, "message", msg) + {:noreply, state} + end + +end diff --git a/lib/cog/chat/hipchat/rooms.ex b/lib/cog/chat/hipchat/rooms.ex new file mode 100644 index 00000000..f0c3b593 --- /dev/null +++ b/lib/cog/chat/hipchat/rooms.ex @@ -0,0 +1,128 @@ +defmodule Cog.Chat.HipChat.Rooms do + + require Logger + + alias Romeo.Connection + alias Romeo.Stanza + + @xmpp_timeout 5000 + + defstruct [:api_token, :hipchat_api_root, :rooms] + + def new(api_root, api_token) do + %__MODULE__{api_token: api_token, hipchat_api_root: api_root, rooms: %{}} + end + + def lookup(%__MODULE__{}=rooms, xmpp_conn, [jid: jid]) do + case Map.get(rooms.rooms, jid) do + nil -> + fetch_xmpp_room(rooms, jid, xmpp_conn) + room -> + {room, rooms} + end + end + def lookup(%__MODULE__{}=rooms, _xmpp_conn, [name: name]) do + case Map.get(rooms.rooms, name) do + nil -> + case fetch_api_room(rooms, name) do + {nil, rooms} -> + {nil, rooms} + {room, rooms} -> + rooms_rooms = rooms.rooms + |> Map.put(room.id, room) + |> Map.put(room.name, room) + {room, %{rooms | rooms: rooms_rooms}} + end + room -> + {room, rooms} + end + end + + def all(%__MODULE__{rooms: rooms}) do + Map.values(rooms) + end + + defp fetch_xmpp_room(rooms, room_jid, xmpp_conn) do + id = "#{:erlang.system_time()}" + xml = [{:xmlel, "query", [{"xmlns", "http://jabber.org/protocol/disco#info"}], []}] + message = %Stanza.IQ{to: room_jid, type: "get", id: id, xml: xml} + Connection.send(xmpp_conn, message) + receive do + {:stanza, %Stanza.IQ{id: ^id}=result} -> + case result.xml do + {:xmlel, "iq", _, [{:xmlel, "query", _, [{:xmlel, "identity", attrs, _}|_]}|_]} -> + room_name = :proplists.get_value("name", attrs, nil) + room = %Cog.Chat.Room{id: room_jid, name: room_name, provider: "hipchat", is_dm: false} + case fetch_api_room(rooms, room_name, room) do + nil -> + {nil, rooms} + room -> + rooms_rooms = rooms.rooms + |> Map.put(room_jid, room) + |> Map.put(room_name, room) + {room, %{rooms | rooms: rooms_rooms}} + end + {:xmlel, "iq", _, [{:xmlel, "query", _, _}, {:xmlel, "error", _, _}|_]} -> + {nil, rooms} + end + {:stanza, %Stanza.IQ{id: ^id, type: "error"}=error} -> + Logger.error("Failed to retrieve room information for JID #{room_jid}: #{inspect error}") + {nil, rooms} + after @xmpp_timeout -> + Logger.error("Room information request for JID #{room_jid} timed out") + {nil, rooms} + end + end + + def fetch_api_room(rooms, name, room \\ nil) + def fetch_api_room(rooms, name, nil) do + url = Enum.join([rooms.hipchat_api_root, "room", name], "/") <> "?auth_token=#{rooms.api_token}" + response = HTTPotion.get(url, headers: ["Accepts": "application/json"]) + case response.status_code do + 404 -> + {nil, rooms} + 200 -> + result = Poison.decode!(response.body) + room = %Cog.Chat.Room{id: Map.get(result, "xmpp_jid"), + secondary_id: ensure_string(Map.get(result, "id")), + name: Map.get(result, "name"), + is_dm: false, + provider: "hipchat"} + rooms_rooms = rooms.rooms + |> Map.put(room.id, room) + |> Map.put(room.secondary_id, room) + {room, %{rooms | rooms: rooms_rooms}} + _ -> + Logger.error("Failed to lookup room '#{name}' via HipChat API: #{inspect response.body}") + {nil, rooms} + end + end + def fetch_api_room(rooms, name, room) do + url = Enum.join([rooms.hipchat_api_root, "room", name], "/") <> "?auth_token=#{rooms.api_token}" + response = HTTPotion.get(url, headers: ["Accepts": "application/json"]) + case response.status_code do + 404 -> + Logger.warn("Unexpected 'not found' result retrieving room info from HipChat API for '#{name}'.") + nil + 200 -> + result = Poison.decode!(response.body) + %{room | secondary_id: ensure_string(Map.get(result, "id"))} + _ -> + Logger.error("Failed to lookuproom '#{name}' via HipChat API: #{inspect response.body}") + nil + end + end + + defp ensure_string(n) when is_integer(n), do: Integer.to_string(n) + defp ensure_string(n) when is_binary(n), do: n +end + +defimpl String.Chars, for: Cog.Chat.HipChat.Rooms do + + alias Cog.Chat.HipChat.Rooms + + def to_string(%Rooms{rooms: rooms}) do + "" + end + +end diff --git a/lib/cog/chat/hipchat/template_processor.ex b/lib/cog/chat/hipchat/template_processor.ex new file mode 100644 index 00000000..1ac1c094 --- /dev/null +++ b/lib/cog/chat/hipchat/template_processor.ex @@ -0,0 +1,135 @@ +defmodule Cog.Chat.HipChat.TemplateProcessor do + require Logger + + @attachment_fields ["footer", "children", "fields", "pretext", "author", "title"] + + def render(directives) do + render_directives(directives) + end + + defp process_directive(%{"name" => "attachment"}=attachment) do + @attachment_fields + |> Enum.reduce([], &(render_attachment(&1, &2, attachment))) + |> List.flatten + |> Enum.join + end + defp process_directive(%{"name" => "text", "text" => text}), + do: text + defp process_directive(%{"name" => "italics", "text" => text}), + do: "#{text}" + defp process_directive(%{"name" => "bold", "text" => text}), + do: "#{text}" + defp process_directive(%{"name" => "fixed_width", "text" => text}), + do: "
#{text}
" + + defp process_directive(%{"name" => "newline"}), do: "
" + + defp process_directive(%{"name" => "unordered_list", "children" => children}) do + items = Enum.map_join(children, &process_directive/1) + "" + end + + defp process_directive(%{"name" => "ordered_list", "children" => children}) do + items = Enum.map_join(children, &process_directive/1) + "
    #{items}
" + end + + defp process_directive(%{"name" => "list_item", "children" => children}) do + children = case List.last(children) do + %{"name" => "newline"} -> + List.delete_at(children, -1) + _ -> + children + end + item = Enum.map_join(children, &process_directive/1) + "
  • #{item}
  • " + end + + defp process_directive(%{"name" => "table", "children" => children}) do + "\n#{render(children)}
    \n" + end + + defp process_directive(%{"name" => "table_header", "children" => children}) do + "#{render(children)}\n" + end + + defp process_directive(%{"name" => "table_row", "children" => children}) do + "#{render(children)}\n" + end + + defp process_directive(%{"name" => "table_cell", "children" => children}) do + "#{render(children)}" + end + + defp process_directive(%{"text" => text}=directive) do + Logger.warn("Unrecognized directive; formatting as plain text: #{inspect directive}") + text + end + defp process_directive(%{"name" => name}=directive) do + Logger.warn("Unrecognized directive; #{inspect directive}") + "
    Unrecognized directive: #{name}
    " + end + + defp render_directives(directives) do + directives + |> Enum.map_join(&process_directive/1) # Convert all Greenbar directives into their HipChat forms + end + + defp render_attachment("footer", acc, attachment) do + case Map.get(attachment, "footer") do + nil -> + acc + footer -> + ["
    #{footer}
    "|acc] + end + end + defp render_attachment("children", acc, attachment) do + case Map.get(attachment, "children") do + nil -> + acc + children -> + [render(children)|acc] + end + end + defp render_attachment("fields", acc, attachment) do + case Map.get(attachment, "fields") do + nil -> + acc + fields -> + rendered_fields = fields + |> Enum.map(fn(%{"title" => title, "value" => value}) -> "#{title}: #{value}
    " end) + |> Enum.join + [rendered_fields <> "
    "|acc] + end + end + defp render_attachment("pretext", acc, attachment) do + case Map.get(attachment, "pretext") do + nil -> + acc + pretext -> + ["#{pretext}
    "|acc] + end + end + defp render_attachment("author", acc, attachment) do + case Map.get(attachment, "author") do + nil -> + acc + author -> + ["Author: #{author}
    "|acc] + end + end + defp render_attachment("title", acc, attachment) do + case Map.get(attachment, "title") do + nil -> + acc + title -> + case Map.get(attachment, "title_url") do + nil -> + ["#{title}

    "|acc] + url -> + ["title

    "|acc] + end + end + end + +end diff --git a/lib/cog/chat/hipchat/users.ex b/lib/cog/chat/hipchat/users.ex new file mode 100644 index 00000000..3de0f9a1 --- /dev/null +++ b/lib/cog/chat/hipchat/users.ex @@ -0,0 +1,71 @@ +defmodule Cog.Chat.HipChat.Users do + + require Logger + + alias Romeo.Roster + alias Cog.Chat.HipChat.Util + + defstruct [users: %{}, last_updated: 0] + + @roster_refresh_interval 120000 + + def lookup(%__MODULE__{}=users, xmpp_conn, jid: jid) do + find_user(users, xmpp_conn, jid) + end + def lookup(%__MODULE__{}=users, xmpp_conn, handle: handle) do + find_user(users, xmpp_conn, handle) + end + def lookup(_users, _conn, args) do + raise RuntimeError, message: "Unknown arg: #{inspect args}" + end + + defp find_user(users, xmpp_conn, handle_or_id) do + case Map.get(users.users, handle_or_id) do + nil -> + case rebuild_users(users, xmpp_conn) do + {true, users} -> + {Map.get(users.users, handle_or_id), users} + {false, users} -> + {nil, users} + error -> + error + end + user -> + {user, users} + end + end + + defp rebuild_users(%__MODULE__{}=users, xmpp_conn) do + ri = System.system_time() - users.last_updated + if ri > @roster_refresh_interval do + try do + roster = Enum.reduce(Roster.items(xmpp_conn), %{}, + fn(item, roster) -> + entry = Util.user_from_roster(item) + roster = if entry.handle != "" do + Map.put(roster, entry.handle, entry) + else + roster + end + roster = if entry.mention_name != "" do + Map.put(roster, entry.mention_name, entry) + else + roster + end + + roster + |> Map.put("#{entry.first_name} #{entry.last_name}", entry) + |> Map.put(entry.id, entry) + end) + {true, %{users | users: roster, last_updated: System.system_time()}} + catch + e -> + Logger.error("Refreshing HipChat roster failed: #{inspect e}") + {:error, :roster_failed} + end + else + {false, users} + end + end + +end diff --git a/lib/cog/chat/hipchat/util.ex b/lib/cog/chat/hipchat/util.ex new file mode 100644 index 00000000..8db09cd1 --- /dev/null +++ b/lib/cog/chat/hipchat/util.ex @@ -0,0 +1,83 @@ +defmodule Cog.Chat.HipChat.Util do + + require Record + + Record.defrecord :xmlel, Record.extract(:xmlel, from_lib: "fast_xml/include/fxml.hrl") + + alias Romeo.Stanza + alias Cog.Chat.User + + def classify_message(%Stanza.Message{}=message) do + case check_invite(message) do + {true, room_name} -> + {:invite, room_name} + false -> + cond do + message.type == "groupchat" -> + case parse_jid(message.from.full) do + nil -> + :ignore + {room_jid, name} -> + {:groupchat, room_jid, name, message.body} + end + message.type == "chat" -> + if message.body == nil or message.body == "" do + :ignore + else + {:dm, message.from.full, message.body} + end + true -> + :ignore + end + end + end + + def user_from_roster(item) do + jid = item.jid.full + [first_name, last_name] = case String.split(item.name, " ", parts: 2) do + [name] -> + [name, ""] + names -> + names + end + {:xmlel, "item", attrs, _} = item.xml + %User{id: jid, + provider: "hipchat", + email: :proplists.get_value("email", attrs, ""), + first_name: first_name, + last_name: last_name, + mention_name: item.name, + handle: :proplists.get_value("mention_name", attrs, "")} + end + + defp check_invite(%Stanza.Message{}=msg) do + check_invite(xmlel(msg.xml, :children)) + end + defp check_invite([]), do: false + defp check_invite([{:xmlel, "x", [{"xmlns", "http://hipchat.com/protocol/muc#room"}], + children}|_]) do + case Enum.reduce_while(children, nil, &extract_room/2) do + nil -> + false + room_name when is_binary(room_name) -> + {true, room_name} + end + end + defp check_invite([_|t]), do: check_invite(t) + + + defp extract_room({:xmlel, "name", _, [xmlcdata: room_name]}, _) do + {:halt, room_name} + end + defp extract_room(_, acc), do: {:cont, acc} + + defp parse_jid(jid) do + case String.split(jid, "/", parts: 2) do + [_] -> + nil + [jid, resource] -> + {jid, resource} + end + end + +end diff --git a/lib/cog/chat/room.ex b/lib/cog/chat/room.ex index c1ab86d9..67476163 100644 --- a/lib/cog/chat/room.ex +++ b/lib/cog/chat/room.ex @@ -3,6 +3,7 @@ defmodule Cog.Chat.Room do use Conduit field :id, :string, required: true + field :secondary_id, :string, required: false field :name, :string, required: true field :provider, :string, required: true field :is_dm, :bool, required: true diff --git a/lib/cog/chat/slack/connector.ex b/lib/cog/chat/slack/connector.ex index 5e1850c3..e437345a 100644 --- a/lib/cog/chat/slack/connector.ex +++ b/lib/cog/chat/slack/connector.ex @@ -41,6 +41,8 @@ defmodule Cog.Chat.Slack.Connector do nil -> send(sender, {ref, {:error, :not_found}}) room -> + # Avoids Slack throttling + jitter() send(sender, {ref, Slack.Web.Channels.join(room["id"], %{token: token})}) end :ok @@ -50,6 +52,8 @@ defmodule Cog.Chat.Slack.Connector do nil -> send(sender, {ref, {:error, :not_found}}) room -> + # Avoids Slack throttling + jitter() send(sender, {ref, Slack.Web.Channels.leave(room["id"], %{token: token})}) end :ok @@ -84,28 +88,31 @@ defmodule Cog.Chat.Slack.Connector do attachments -> Map.put(message, :attachments, Poison.encode!(attachments)) end + # Avoids Slack throttling + jitter() result = Slack.Web.Chat.post_message(target, message) send(sender, {ref, result}) :ok end def handle_info({{ref, sender}, {:lookup_room, %{id: id, token: token}}}, state) do result = case classify_id(id) do - :user -> - case open_dm(id, token) do - {:ok, room} -> - {:ok, room} - {:error, error} -> - Logger.warn("Could not establish an IM with user #{Slack.Lookups.lookup_user_name(id, state)} (Slack ID: #{id}): #{inspect error}") - {:error, :user_not_found} - end - :channel -> - case lookup_room(id, joined_channels(state), by: :id) do - %Room{}=room -> {:ok, room} - _ -> {:error, :not_a_member} - end - :error -> - {:error, :not_found} - end + :user -> + # Avoids Slack throttling + case open_dm(id, token) do + {:ok, room} -> + {:ok, room} + {:error, error} -> + Logger.warn("Could not establish an IM with user #{Slack.Lookups.lookup_user_name(id, state)} (Slack ID: #{id}): #{inspect error}") + {:error, :user_not_found} + end + :channel -> + case lookup_room(id, joined_channels(state), by: :id) do + %Room{}=room -> {:ok, room} + _ -> {:error, :not_a_member} + end + :error -> + {:error, :not_found} + end send(sender, {ref, result}) :ok @@ -173,6 +180,8 @@ defmodule Cog.Chat.Slack.Connector do end defp open_dm(user_id, token) do + # Avoids Slack throttling + jitter() case Slack.Web.Im.open(user_id, %{token: token}) do %{"channel" => %{"id" => id}} -> {:ok, %Room{id: id, @@ -235,6 +244,7 @@ defmodule Cog.Chat.Slack.Connector do first_name: user.name, last_name: user.name, handle: user.name, + mention_name: user.name, provider: "slack"} else profile = user.profile @@ -242,6 +252,7 @@ defmodule Cog.Chat.Slack.Connector do first_name: Map.get(profile, :first_name, user.name), last_name: Map.get(profile, :last_name, user.name), handle: user.name, + mention_name: user.name, email: profile.email, provider: "slack"} end @@ -320,4 +331,10 @@ defmodule Cog.Chat.Slack.Connector do Logger.warn("Could not classify Slack id `#{other}`") :error end + + defp jitter() do + n = :random.uniform(90) + 10 + :timer.sleep((n * 10)) + end + end diff --git a/lib/cog/chat/slack/provider.ex b/lib/cog/chat/slack/provider.ex index e2d2daee..b35a0ae5 100644 --- a/lib/cog/chat/slack/provider.ex +++ b/lib/cog/chat/slack/provider.ex @@ -14,7 +14,12 @@ defmodule Cog.Chat.Slack.Provider do def display_name, do: "Slack" def start_link(config) do - GenServer.start_link(__MODULE__, [config], name: __MODULE__) + case Application.ensure_all_started(:slack) do + {:ok, _} -> + GenServer.start_link(__MODULE__, [config], name: __MODULE__) + error -> + error + end end def mention_name(handle) do diff --git a/lib/cog/chat/user.ex b/lib/cog/chat/user.ex index 0a19704a..c058606d 100644 --- a/lib/cog/chat/user.ex +++ b/lib/cog/chat/user.ex @@ -8,5 +8,6 @@ defmodule Cog.Chat.User do field :first_name, :string, required: true field :last_name, :string, required: true field :handle, :string, required: true + field :mention_name, :string, required: false end diff --git a/lib/cog/chat_sup.ex b/lib/cog/chat_sup.ex new file mode 100644 index 00000000..f69ca293 --- /dev/null +++ b/lib/cog/chat_sup.ex @@ -0,0 +1,14 @@ +defmodule Cog.ChatSup do + + require Logger + use Supervisor + + def start_link(), do: Supervisor.start_link(__MODULE__, []) + + def init(_) do + children = [supervisor(Cog.Chat.Http.Supervisor, []), + worker(Cog.Chat.Adapter, [])] + {:ok, {%{strategy: :one_for_one, intensity: 10, period: 60}, children}} + end + +end diff --git a/lib/cog/command/pipeline/initializer.ex b/lib/cog/command/pipeline/initializer.ex index 98b4b202..e52aedce 100644 --- a/lib/cog/command/pipeline/initializer.ex +++ b/lib/cog/command/pipeline/initializer.ex @@ -38,7 +38,7 @@ defmodule Cog.Command.Pipeline.Initializer do # and the incoming request is from Slack. # # TODO: should only do this if the adapter is a chat adapter - self_register_flag = Application.get_env(:cog, :self_registration, false) and payload.adapter == "slack" + self_register_flag = Application.get_env(:cog, :self_registration, false) and payload.adapter != "http" case self_register_user(payload, self_register_flag, state) do :ok -> # TODO: should only do history check if the adapter is a chat diff --git a/lib/cog/core_sup.ex b/lib/cog/core_sup.ex index 172c025b..a73f98ed 100644 --- a/lib/cog/core_sup.ex +++ b/lib/cog/core_sup.ex @@ -14,8 +14,7 @@ defmodule Cog.CoreSup do supervisor(Cog.Endpoint, []), supervisor(Cog.TriggerEndpoint, []), supervisor(Cog.ServiceEndpoint, []), - supervisor(Cog.Chat.Http.Supervisor,[]), - worker(Cog.Chat.Adapter, [])] + supervisor(Cog.ChatSup, [])] {:ok, {%{strategy: :one_for_one, intensity: 10, period: 60}, children}} end diff --git a/lib/cog/models/chat_provider.ex b/lib/cog/models/chat_provider.ex index 8fea843e..2ebf80d1 100644 --- a/lib/cog/models/chat_provider.ex +++ b/lib/cog/models/chat_provider.ex @@ -5,10 +5,19 @@ defmodule Cog.Models.ChatProvider do schema "chat_providers" do field :name, :string - + field :data, :map timestamps end + @required_fields ~w(name) + @optional_fields ~w(data) + summary_fields [:id, :name] detail_fields [:id, :name] + + def changeset(model, params \\ :empty) do + model + |> cast(params, @required_fields, @optional_fields) + end +P end diff --git a/lib/cog/repository/chat_providers.ex b/lib/cog/repository/chat_providers.ex new file mode 100644 index 00000000..8c6041ee --- /dev/null +++ b/lib/cog/repository/chat_providers.ex @@ -0,0 +1,34 @@ +defmodule Cog.Repository.ChatProviders do + + @moduledoc """ + Returns the persistent state for a given chat provider + """ + + @type error :: Ecto.Changeset.error | atom + + alias Cog.Repo + alias Cog.Models.ChatProvider + + @spec get_provider_state(binary()) :: nil | map() + def get_provider_state(name, default \\ %{}) when is_binary(name) do + case Repo.get_by(ChatProvider, name: name) do + nil -> + nil + provider -> + provider.data || default + end + end + + @spec set_provider_state(binary(), map()) :: {:ok, map()} | {:error, [error]} + def set_provider_state(name, state) when is_binary(name) and is_map(state) do + provider = Repo.get_by!(ChatProvider, name: name) + changeset = ChatProvider.changeset(provider, %{"data" => state}) + case Repo.update(changeset) do + {:ok, updated} -> + {:ok, updated.data} + {:error, changeset} -> + {:error, changeset.errors} + end + end + +end diff --git a/mix.exs b/mix.exs index e42e286a..432db273 100644 --- a/mix.exs +++ b/mix.exs @@ -27,7 +27,7 @@ defmodule Cog.Mixfile do def application do [applications: started_applications, - included_applications: [:emqttd], + included_applications: [:emqttd, :slack, :romeo], mod: {Cog, []}] end @@ -40,7 +40,6 @@ defmodule Cog.Mixfile do :httpotion, :gproc, :esockd, - :slack, :cowboy, :phoenix, :phoenix_ecto, @@ -104,6 +103,7 @@ defmodule Cog.Mixfile do {:slack, github: "operable/Elixir-Slack", branch: "allow-attachments"}, {:table_rex, "~> 0.8"}, {:uuid, "~> 1.1.3"}, + {:romeo, github: "operable/romeo", branch: "iq-bodies"}, # The Slack library depends on this Github repo, and not the # version in Hex. Thus, we need to declare it manually :( {:websocket_client, github: "jeremyong/websocket_client"}, diff --git a/mix.lock b/mix.lock index e3492ebf..8d1f6b35 100644 --- a/mix.lock +++ b/mix.lock @@ -22,6 +22,7 @@ "excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, "exjsx": {:hex, :exjsx, "3.2.0", "7136cc739ace295fc74c378f33699e5145bead4fdc1b4799822d0287489136fb", [:mix], [{:jsx, "~> 2.6.2", [hex: :jsx, optional: false]}]}, "exvcr": {:hex, :exvcr, "0.8.2", "8ad46e077995e1ff1278b44001844fcd1e9c172bd1d9f6303e6c8f9ba8bbb679", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, optional: false]}, {:exjsx, "~> 3.2", [hex: :exjsx, optional: false]}, {:httpoison, "~> 0.8", [hex: :httpoison, optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, optional: true]}, {:ibrowse, "~> 4.2.2", [hex: :ibrowse, optional: true]}, {:meck, "~> 0.8.3", [hex: :meck, optional: false]}]}, + "fast_xml": {:hex, :fast_xml, "1.1.15", "6d23eb7f874e1357cf80a48d75a7bd0c8f6318029dc4b70122e9f54911f57f83", [:rebar3], [{:p1_utils, "1.0.5", [hex: :p1_utils, optional: false]}]}, "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, "fumanchu": {:git, "https://github.com/operable/fumanchu.git", "8ad13215241d928d55bd488ee512bc8d835996c1", []}, "gen_logger": {:git, "https://github.com/emqtt/gen_logger.git", "530af9cc2babcb77c541d46af34a9879d4a5ac07", [branch: "master"]}, @@ -48,6 +49,7 @@ "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mix_test_watch": {:hex, :mix_test_watch, "0.2.6", "9fcc2b1b89d1594c4a8300959c19d50da2f0ff13642c8f681692a6e507f92cab", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}]}, "mochiweb": {:git, "git://github.com/emqtt/mochiweb.git", "aaa7815f3d6b16e4a14531bc24826af060f44ee4", [tag: "4.1"]}, + "p1_utils": {:hex, :p1_utils, "1.0.5", "3e698354fdc1fea5491d991457b0cb986c0a00a47d224feb841dc3ec82b9f721", [:rebar3], []}, "phoenix": {:hex, :phoenix, "1.2.1", "6dc592249ab73c67575769765b66ad164ad25d83defa3492dc6ae269bd2a68ab", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.1", [hex: :plug, optional: false]}, {:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.0.1", "42eb486ef732cf209d0a353e791806721f33ff40beab0a86f02070a5649ed00a", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.6", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]}, "phoenix_html": {:hex, :phoenix_html, "2.6.2", "944a5e581b0d899e4f4c838a69503ebd05300fe35ba228a74439e6253e10e0c0", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]}, @@ -60,6 +62,7 @@ "postgrex": {:hex, :postgrex, "0.11.2", "139755c1359d3c5c6d6e8b1ea72556d39e2746f61c6ddfb442813c91f53487e8", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.0-rc", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]}, "probe": {:git, "https://github.com/operable/probe.git", "f2197509f01922a28219ab47af4e5c6324f47ac6", []}, "ranch": {:hex, :ranch, "1.2.1", "a6fb992c10f2187b46ffd17ce398ddf8a54f691b81768f9ef5f461ea7e28c762", [:make], []}, + "romeo": {:git, "https://github.com/operable/romeo.git", "65106b94f134a12791ec63fa284b0ce5e9358538", [branch: "iq-bodies"]}, "slack": {:git, "https://github.com/operable/Elixir-Slack.git", "a0dae7d46a10f9d7e7924a1c8fe5cef17b7cfb82", [branch: "allow-attachments"]}, "spanner": {:git, "https://github.com/operable/spanner.git", "d336e5b63afe8ca4e66d9cc8c476413d5edcf53a", []}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}, diff --git a/priv/repo/migrations/20160926135327_add_chat_provider_state.exs b/priv/repo/migrations/20160926135327_add_chat_provider_state.exs new file mode 100644 index 00000000..4b9dbff2 --- /dev/null +++ b/priv/repo/migrations/20160926135327_add_chat_provider_state.exs @@ -0,0 +1,10 @@ +defmodule Cog.Repo.Migrations.AddChatProviderState do + use Ecto.Migration + + def change do + alter table(:chat_providers) do + add :data, :jsonb, null: true + end + end + +end diff --git a/test/cog/chat/all_templates_test.exs b/test/cog/chat/all_templates_test.exs index ca798a9c..66cc1be5 100644 --- a/test/cog/chat/all_templates_test.exs +++ b/test/cog/chat/all_templates_test.exs @@ -26,6 +26,30 @@ defmodule Cog.Chat.AllTemplatesTest do end end + test "all embedded templates have HipChat tests" do + {have_tests, no_tests} = tested_untested("hipchat") + if no_tests == [] do + :ok + else + flunk """ + The following embedded bundle templates do not have Slack + rendering tests defined: + + #{Enum.join(no_tests, "\n")} + + Tests were found for the following templates, however: + + #{Enum.join(have_tests, "\n")} + + NOTE: This just tests for the presence of a dedicated test file + for each template; it does not make any claims to the + thoroughness of the tests within those files. + + """ + end + end + + def tested_untested(provider) do Enum.partition(embedded_template_names, &test_file_exists?(provider, &1)) diff --git a/test/cog/chat/hipchat/template_processor_test.exs b/test/cog/chat/hipchat/template_processor_test.exs new file mode 100644 index 00000000..533d1f22 --- /dev/null +++ b/test/cog/chat/hipchat/template_processor_test.exs @@ -0,0 +1,107 @@ +defmodule Cog.Chat.HipChat.TemplateProcessorTest do + use ExUnit.Case + + alias Cog.Chat.HipChat.TemplateProcessor + + test "processes a list of directives" do + directives = [ + %{"name" => "text", + "text" => "This is a rendering test. First, let's try italics: "}, + %{"name" => "italics", + "text" => "I'm italic text!"}, + %{"name" => "text", + "text" => "\nThat was fun; now let's do bold: "}, + %{"name" => "bold", + "text" => "BEHOLD! BOLD!"}, + %{"name" => "text", + "text" => "\nFascinating. How about some fixed width text? "}, + %{"name" => "fixed_width", + "text" => "BEEP BOOP... I AM A ROBOT... BEEP BOOP"}, + %{"name" => "text", + "text" => "\nWow, good stuff. And now... AN ASCII TABLE!\n\n"}, + %{"name" => "table", + "children" => [%{"name" => "table_header", + "children" => [ + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Member"}]}, + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Instrument"}]}]}, + %{"name" => "table_row", + "children" => [ + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Geddy Lee"}]}, + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Vocals, Bass, Keyboards"}]}]}, + %{"name" => "table_row", + "children" => [ + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Alex Lifeson"}]}, + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Guitar"}]}]}, + %{"name" => "table_row", + "children" => [ + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Neal Peart"}]}, + %{"name" => "table_cell", + "children" => [ + %{"name" => "text", + "text" => "Drums, Percussion"}]}]}]}, + %{"name" => "text", + "text" => "\nHow do you like them apples?"}] + + rendered = TemplateProcessor.render(directives) + expected = """ + This is a rendering test. First, let's try italics: I'm italic text! + That was fun; now let's do bold: BEHOLD! BOLD! + Fascinating. How about some fixed width text?
    BEEP BOOP... I AM A ROBOT... BEEP BOOP
    + Wow, good stuff. And now... AN ASCII TABLE! + + + + + + +
    MemberInstrument
    Geddy LeeVocals, Bass, Keyboards
    Alex LifesonGuitar
    Neal PeartDrums, Percussion
    + + How do you like them apples? + """ |> String.trim + assert expected == rendered + end + + + test "handles unrecognized text directives" do + directives = [ + %{"name" => "text", "text" => "Important message: "}, + %{"name" => "wat", "text" => "whatever"} + ] + + rendered = TemplateProcessor.render(directives) + assert "Important message: whatever" == rendered + end + + test "completely unrecognized directives get rendered directly" do + directives = [ + %{"name" => "text", "text" => "Important message: "}, + %{"name" => "wat", "something" => "whatever", "meaning_of_life" => 42} + ] + + rendered = TemplateProcessor.render(directives) + expected = "Important message:
    Unrecognized directive: wat
    " + + assert expected == rendered + end +end diff --git a/test/cog/chat/hipchat/templates/common/error_test.exs b/test/cog/chat/hipchat/templates/common/error_test.exs new file mode 100644 index 00000000..edc5b234 --- /dev/null +++ b/test/cog/chat/hipchat/templates/common/error_test.exs @@ -0,0 +1,46 @@ +defmodule Cog.Chat.HipChat.Templates.Common.ErrorTest do + use Cog.TemplateCase + + test "error template; planning failure" do + data = %{"id" => "deadbeef", + "started" => "some time in the past", + "initiator" => "somebody", + "pipeline_text" => "echo foo", + "error_message" => "bad stuff happened", + "planning_failure" => "I can't plan this!", + "execution_failure" => false} + + directives = directives_for_template(:common, "error", data) + rendered = Cog.Chat.HipChat.TemplateProcessor.render(directives) + assert "Command Error

    " <> + "Started: some time in the past
    " <> + "Pipeline ID: deadbeef
    " <> + "Pipeline: echo foo
    " <> + "Caller: somebody

    " <> + "The pipeline failed planning the invocation:

    " <> + "
    I can't plan this!\n


    " <> + "The specific error was:

    bad stuff happened\n
    " == rendered + end + + test "error template; execution failure" do + + data = %{"id" => "deadbeef", + "started" => "some time in the past", + "initiator" => "somebody", + "pipeline_text" => "echo foo", + "error_message" => "bad stuff happened", + "planning_failure" => false, + "execution_failure" => "I can't execute this!"} + directives = directives_for_template(:common, "error", data) + rendered = Cog.Chat.HipChat.TemplateProcessor.render(directives) + assert "Command Error

    " <> + "Started: some time in the past
    " <> + "Pipeline ID: deadbeef
    " <> + "Pipeline: echo foo
    " <> + "Caller: somebody

    " <> + "The pipeline failed executing the command:

    " <> + "
    I can't execute this!\n


    " <> + "The specific error was:

    bad stuff happened\n
    " == rendered + end + +end diff --git a/test/cog/chat/hipchat/templates/common/raw_test.exs b/test/cog/chat/hipchat/templates/common/raw_test.exs new file mode 100644 index 00000000..7bc3575b --- /dev/null +++ b/test/cog/chat/hipchat/templates/common/raw_test.exs @@ -0,0 +1,10 @@ +defmodule Cog.Chat.HipChat.Templates.Common.RawTest do + use Cog.TemplateCase + + test "raw renders properly" do + data = %{"results" => [%{"foo" => "bar"}]} + expected = "
    [\n  {\n    \"foo\": \"bar\"\n  }\n]
    " + assert_rendered_template(:hipchat, :common, "raw", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/common/self_registration_test.exs b/test/cog/chat/hipchat/templates/common/self_registration_test.exs new file mode 100644 index 00000000..26219cfb --- /dev/null +++ b/test/cog/chat/hipchat/templates/common/self_registration_test.exs @@ -0,0 +1,24 @@ +defmodule Cog.Chat.HipChat.Templates.Common.SelfRegistrationTest do + use Cog.TemplateCase + + test "self_registration_success renders properly" do + data = %{"mention_name" => "@cog", + "first_name" => "Cog", + "username" => "cog"} + expected = """ + @cog: Hello Cog! It's great to meet you! You're the proud owner of a shiny new Cog account named 'cog'. + """ |> String.strip + assert_rendered_template(:hipchat, :common, "self-registration-success", data, expected) + end + + test "self_registration_failed renders properly" do + data = %{"mention_name" => "@mystery_user", + "display_name" => "HipChat"} + expected = "@mystery_user: Unfortunately I was unable to automatically create a " <> + "Cog account for your HipChat chat handle. Only users with Cog accounts can interact with me." <> + "

    " <> + "You'll need to ask a Cog administrator to investigate the situation and set up your account." + assert_rendered_template(:hipchat, :common, "self-registration-failed", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/common/unregistered_user_test.exs b/test/cog/chat/hipchat/templates/common/unregistered_user_test.exs new file mode 100644 index 00000000..36fd7c3f --- /dev/null +++ b/test/cog/chat/hipchat/templates/common/unregistered_user_test.exs @@ -0,0 +1,33 @@ +defmodule Cog.Chat.HipChat.Templates.Common.UnregisteredUserTest do + use Cog.TemplateCase + + test "unregistered_user renders properly with user creators" do + data = %{"handle" => "nobody", + "mention_name" => "@nobody", + "display_name" => "HipChat", + "user_creators" => ["@larry", "@moe", "@curly"]} + expected = "@nobody: I'm terribly sorry, but either I don't have a Cog account for you, or your " <> + "HipChat chat handle has not been registered. Currently, only registered users can interact with me." <> + "

    " <> + "You'll need to ask a Cog administrator to fix this situation and to register your HipChat handle." <> + "

    " <> + "The following users can help you right here in chat:" <> + "

    " <> + "@larry
    @moe
    @curly" + assert_rendered_template(:hipchat, :common, "unregistered-user", data, expected) + end + + test "unregistered_user renders properly without user creators" do + data = %{"handle" => "nobody", + "mention_name" => "@nobody", + "display_name" => "HipChat", + "user_creators" => []} + expected = "@nobody: I'm terribly sorry, but either I don't have a Cog account for you, or your " <> + "HipChat chat handle has not been registered. Currently, only registered users can interact with me." <> + "

    " <> + "You'll need to ask a Cog administrator to fix this situation and to register your HipChat handle." + assert_rendered_template(:hipchat, :common, "unregistered-user", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/alias_create_test.exs b/test/cog/chat/hipchat/templates/embedded/alias_create_test.exs new file mode 100644 index 00000000..b8a66491 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/alias_create_test.exs @@ -0,0 +1,20 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.AliasCreateTest do + use Cog.TemplateCase + + test "alias-create template" do + data = %{"results" => [%{"name" => "awesome_alias"}]} + expected = "Alias 'user:awesome_alias' has been created" + assert_rendered_template(:hipchat, :embedded, "alias-create", data, expected) + end + + test "alias-create with multiple inputs" do + data = %{"results" => [%{"name" => "awesome_alias"}, + %{"name" => "another_awesome_alias"}, + %{"name" => "wow_neat"}]} + expected = "Alias 'user:awesome_alias' has been created
    " <> + "Alias 'user:another_awesome_alias' has been created
    " <> + "Alias 'user:wow_neat' has been created" + + assert_rendered_template(:hipchat, :embedded, "alias-create", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/alias_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/alias_delete_test.exs new file mode 100644 index 00000000..0a4add20 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/alias_delete_test.exs @@ -0,0 +1,20 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.AliasDeleteTest do + use Cog.TemplateCase + + test "alias-delete template" do + data = %{"results" => [%{"visibility" => "user", "name" => "awesome_alias"}]} + expected = "Deleted 'user:awesome_alias'" + assert_rendered_template(:hipchat, :embedded, "alias-delete", data, expected) + end + + test "alias-delete with multiple inputs" do + data = %{"results" => [%{"visibility" => "user", "name" => "awesome_alias"}, + %{"visibility" => "user", "name" => "another_awesome_alias"}, + %{"visibility" => "site", "name" => "wow_neat"}]} + expected = "Deleted 'user:awesome_alias'
    " <> + "Deleted 'user:another_awesome_alias'
    " <> + "Deleted 'site:wow_neat'" + assert_rendered_template(:hipchat, :embedded, "alias-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/alias_list_test.exs b/test/cog/chat/hipchat/templates/embedded/alias_list_test.exs new file mode 100644 index 00000000..6a9f5507 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/alias_list_test.exs @@ -0,0 +1,28 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.AliasListTest do + use Cog.TemplateCase + + test "alias-list template" do + data = %{"results" => [%{"visibility" => "user", + "name" => "awesome_alias", + "pipeline" => "echo 'awesome!'"}, + %{"visibility" => "user", + "name" => "another_awesome_alias", + "pipeline" => "echo 'more awesome!'"}, + %{"visibility" => "site", + "name" => "wow_neat", + "pipeline" => "echo 'wow, neat!'"}]} + expected = "Found 3 matching aliases.

    " <> + "Name:
    awesome_alias

    " <> + "Visibility:
    user

    " <> + "Pipeline:
    echo 'awesome!'


    " <> + "Name:
    another_awesome_alias

    " <> + "Visibility:
    user

    " <> + "Pipeline:
    echo 'more awesome!'


    " <> + "Name:
    wow_neat

    " <> + "Visibility:
    site

    " <> + "Pipeline:
    echo 'wow, neat!'
    " + + assert_rendered_template(:hipchat, :embedded, "alias-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/alias_move_test.exs b/test/cog/chat/hipchat/templates/embedded/alias_move_test.exs new file mode 100644 index 00000000..e780d2d0 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/alias_move_test.exs @@ -0,0 +1,32 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.AliasMoveTest do + use Cog.TemplateCase + + test "alias-move template" do + data = %{"results" => [%{"source" => %{"visibility" => "user", + "name" => "awesome"}, + "destination" => %{"visibility" => "site", + "name" => "awesome"}}]} + expected = "Successfully moved user:awesome to site:awesome" + assert_rendered_template(:hipchat, :embedded, "alias-move", data, expected) + end + + test "alias-move with multiple inputs" do + data = %{"results" => [%{"source" => %{"visibility" => "user", + "name" => "awesome"}, + "destination" => %{"visibility" => "site", + "name" => "awesome"}}, + %{"source" => %{"visibility" => "user", + "name" => "do_stuff"}, + "destination" => %{"visibility" => "site", + "name" => "do_stuff"}}, + %{"source" => %{"visibility" => "user", + "name" => "thingie"}, + "destination" => %{"visibility" => "site", + "name" => "dohickey"}}]} + expected = "Successfully moved user:awesome to site:awesome
    " <> + "Successfully moved user:do_stuff to site:do_stuff
    " <> + "Successfully moved user:thingie to site:dohickey" + assert_rendered_template(:hipchat, :embedded, "alias-move", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_disable_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_disable_test.exs new file mode 100644 index 00000000..740adac0 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_disable_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleDisableTest do + use Cog.TemplateCase + + test "bundle-disable template" do + data = %{"results" => [%{"name" => "foo", + "version" => "1.0.0"}]} + expected = ~s(Bundle "foo" version "1.0.0" has been disabled.) + assert_rendered_template(:hipchat, :embedded, "bundle-disable", data, expected) + end + + test "bundle-disable template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", "version" => "1.0.0"}, + %{"name" => "bar", "version" => "2.0.0"}, + %{"name" => "baz", "version" => "3.0.0"}]} + expected = "Bundle \"foo\" version \"1.0.0\" has been disabled.
    " <> + "Bundle \"bar\" version \"2.0.0\" has been disabled.
    " <> + "Bundle \"baz\" version \"3.0.0\" has been disabled." + assert_rendered_template(:hipchat, :embedded, "bundle-disable", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_enable_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_enable_test.exs new file mode 100644 index 00000000..0ffdf76c --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_enable_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleEnableTest do + use Cog.TemplateCase + + test "bundle-enable template" do + data = %{"results" => [%{"name" => "foo", + "version" => "1.0.0"}]} + expected = ~s(Bundle "foo" version "1.0.0" has been enabled.) + assert_rendered_template(:hipchat, :embedded, "bundle-enable", data, expected) + end + + test "bundle-enable template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", "version" => "1.0.0"}, + %{"name" => "bar", "version" => "2.0.0"}, + %{"name" => "baz", "version" => "3.0.0"}]} + expected = "Bundle \"foo\" version \"1.0.0\" has been enabled.
    " <> + "Bundle \"bar\" version \"2.0.0\" has been enabled.
    " <> + "Bundle \"baz\" version \"3.0.0\" has been enabled." + assert_rendered_template(:hipchat, :embedded, "bundle-enable", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_info_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_info_test.exs new file mode 100644 index 00000000..ac524f33 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_info_test.exs @@ -0,0 +1,24 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleInfoTest do + use Cog.TemplateCase + + test "bundle-info template" do + data = %{"results" => [%{"id" => "aaaa-bbbb-cccc-dddd-eeee-ffff", + "name" => "my_bundle", + "versions" => [%{"version" => "0.0.1"}, + %{"version" => "0.0.2"}, + %{"version" => "0.0.3"}], + "enabled_version" => %{"version" => "0.0.3"}, + "relay_groups" => [%{"name" => "preprod"}, + %{"name" => "prod"}]}]} + expected = "ID: aaaa-bbbb-cccc-dddd-eeee-ffff
    " <> + "Name: my_bundle
    " <> + "Versions: 0.0.1
    " <> + "0.0.2
    " <> + "0.0.3
    " <> + "Enabled Version: 0.0.3
    " <> + "Relay Groups: preprod
    " <> + "prod" + assert_rendered_template(:hipchat, :embedded, "bundle-info", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_install_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_install_test.exs new file mode 100644 index 00000000..f9e620b0 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_install_test.exs @@ -0,0 +1,13 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleInstallTest do + use Cog.TemplateCase + + test "bundle-info template" do + data = %{"results" => [%{"name" => "heroku", + "versions" => [%{"version" => "0.0.4"}]}]} + + expected = "Bundle \"heroku\" version \"0.0.4\" installed." + + assert_rendered_template(:hipchat, :embedded, "bundle-install", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_list_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_list_test.exs new file mode 100644 index 00000000..c195573d --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_list_test.exs @@ -0,0 +1,44 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleListTest do + use Cog.TemplateCase + + test "bundle-list template" do + data = %{"results" => [%{"name" => "test_bundle1", + "enabled_version" => %{"version" => "1.0.0"}}, + %{"name" => "test_bundle2", + "enabled_version" => %{"version" => "2.0.0"}}, + %{"name" => "test_bundle3", + "enabled_version" => %{"version" => "3.0.0"}}, + %{"name" => "test_bundle4", + "enabled_version" => %{"version" => "4.0.0"}}]} + + expected = """ + + + + + + +
    NameEnabled Version
    test_bundle11.0.0
    test_bundle22.0.0
    test_bundle33.0.0
    test_bundle44.0.0
    + """ + + assert_rendered_template(:hipchat, :embedded, "bundle-list", data, expected) + end + + test "handles disabled bundles properly" do + data = %{"results" => [%{"name" => "test_bundle1"}, + %{"name" => "test_bundle2", + "enabled_version" => %{"version" => "2.0.0"}}]} + + + expected = """ + + + + +
    NameEnabled Version
    test_bundle1
    test_bundle22.0.0
    + """ + + assert_rendered_template(:hipchat, :embedded, "bundle-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/bundle_versions_test.exs b/test/cog/chat/hipchat/templates/embedded/bundle_versions_test.exs new file mode 100644 index 00000000..fa488282 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/bundle_versions_test.exs @@ -0,0 +1,30 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.BundleVersionsTest do + use Cog.TemplateCase + + test "bundle-versions template" do + data = %{"results" => [%{"name" => "test_bundle1", + "version" => "1.0.0", + "enabled" => true}, + %{"name" => "test_bundle1", + "version" => "0.0.9", + "enabled" => false}, + %{"name" => "test_bundle1", + "version" => "0.0.8", + "enabled" => false}, + %{"name" => "test_bundle1", + "version" => "0.0.7", + "enabled" => false}]} + expected = """ + + + + + + +
    VersionEnabled?
    1.0.0true
    0.0.9false
    0.0.8false
    0.0.7false
    + """ + + assert_rendered_template(:hipchat, :embedded, "bundle-versions", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/help_bundle_test.exs b/test/cog/chat/hipchat/templates/embedded/help_bundle_test.exs new file mode 100644 index 00000000..dfbd0b52 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/help_bundle_test.exs @@ -0,0 +1,27 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.HelpBundleTest do + use Cog.TemplateCase + + test "help-bundles template" do + data = %{"results" => [%{"name" => "test-bundle", + "description" => "Does a thing", + "long_description" => "No really, it does a thing", + "commands" => [%{"name" => "test-command", + "description" => "does just one thing"}], + "author" => "vanstee", + "homepage" => "test-bundle.com"}]} + + + expected = "Name

    " <> + "test-bundle - Does a thing

    " <> + "Description

    " <> + "No really, it does a thing

    " <> + "Commands

    " <> + "
    " <> + "Author

    " <> + "vanstee

    " <> + "Homepage

    " <> + "test-bundle.com" + + assert_rendered_template(:hipchat, :embedded, "help-bundle", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/help_bundles_test.exs b/test/cog/chat/hipchat/templates/embedded/help_bundles_test.exs new file mode 100644 index 00000000..12865053 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/help_bundles_test.exs @@ -0,0 +1,16 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.HelpBundlesTest do + use Cog.TemplateCase + + test "help-bundles template" do + data = %{"results" => [%{"enabled" => [%{"name" => "operable"}], + "disabled" => [%{"name" => "test-bundle"}]}]} + + expected = "Enabled Bundles

    " <> + "
    " <> + "Disabled Bundles

    " <> + "
    " <> + "To learn more about a specific bundle and the commands available within it, you can use \"operable:help \"." + + assert_rendered_template(:hipchat, :embedded, "help-bundles", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/help_command_documentation_test.exs b/test/cog/chat/hipchat/templates/embedded/help_command_documentation_test.exs new file mode 100644 index 00000000..f2bd1697 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/help_command_documentation_test.exs @@ -0,0 +1,9 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.HelpCommandDocumentationTest do + use Cog.TemplateCase + + test "help-command-documentation template" do + data = %{"results" => [%{"documentation" => "big ol' doc string"}]} + expected = "
    big ol' doc string\n
    " + assert_rendered_template(:hipchat, :embedded, "help-command-documentation", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/help_command_test.exs b/test/cog/chat/hipchat/templates/embedded/help_command_test.exs new file mode 100644 index 00000000..f7f54228 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/help_command_test.exs @@ -0,0 +1,19 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.HelpCommandTest do + use Cog.TemplateCase + + test "help-command template" do + data = %{"results" => [%{"name" => "test", + "description" => "Do a test thing", + "synopsis" => "test --do-a-thing", + "bundle" => %{"author" => "vanstee"}}]} + expected = "Name

    " <> + "test - Do a test thing

    " <> + "Synopsis

    " <> + "
    test --do-a-thing\n

    " <> + "Author

    " <> + "vanstee
    " + + assert_rendered_template(:hipchat, :embedded, "help-command", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/help_test.exs b/test/cog/chat/hipchat/templates/embedded/help_test.exs new file mode 100644 index 00000000..ca07323f --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/help_test.exs @@ -0,0 +1,14 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.HelpTest do + use Cog.TemplateCase + + test "help template" do + data = %{"results" => [%{"bundle" => %{"name" => "test"}, "name" => "one"}, + %{"bundle" => %{"name" => "test"}, "name" => "two"}, + %{"bundle" => %{"name" => "test"}, "name" => "three"}]} + expected = "Here are the commands I know about:

    " <> + "
    1. test:one
    2. test:two
    3. test:three

    " <> + "Have a nice day!" + + assert_rendered_template(:hipchat, :embedded, "help", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_create_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_create_test.exs new file mode 100644 index 00000000..fbdce446 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionCreateTest do + use Cog.TemplateCase + + test "permission-create template" do + data = %{"results" => [%{"bundle" => "site", "name" => "foo"}]} + expected = "Created permission 'site:foo'" + assert_rendered_template(:hipchat, :embedded, "permission-create", data, expected) + end + + test "permission-create template with multiple inputs" do + data = %{"results" => [%{"bundle" => "site", "name" => "foo"}, + %{"bundle" => "site", "name" => "bar"}, + %{"bundle" => "site", "name" => "baz"}]} + expected = "Created permission 'site:foo'
    " <> + "Created permission 'site:bar'
    " <> + "Created permission 'site:baz'" + + assert_rendered_template(:hipchat, :embedded, "permission-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_delete_test.exs new file mode 100644 index 00000000..ba075244 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionDeleteTest do + use Cog.TemplateCase + + test "permission-delete template" do + data = %{"results" => [%{"bundle" => "site", "name" => "foo"}]} + expected = "Deleted permission 'site:foo'" + assert_rendered_template(:hipchat, :embedded, "permission-delete", data, expected) + end + + test "permission-delete template with multiple inputs" do + data = %{"results" => [%{"bundle" => "site", "name" => "foo"}, + %{"bundle" => "site", "name" => "bar"}, + %{"bundle" => "site", "name" => "baz"}]} + expected = "Deleted permission 'site:foo'
    " <> + "Deleted permission 'site:bar'
    " <> + "Deleted permission 'site:baz'" + + assert_rendered_template(:hipchat, :embedded, "permission-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_grant_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_grant_test.exs new file mode 100644 index 00000000..39c1ef09 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_grant_test.exs @@ -0,0 +1,30 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionGrantTest do + use Cog.TemplateCase + + test "permission-grant template" do + data = %{"results" => [%{"permission" => %{"bundle" => "site", + "name" => "foo"}, + "role" => %{"name" => "ops"}}]} + + expected = "Granted permission 'site:foo' to role 'ops'" + assert_rendered_template(:hipchat, :embedded, "permission-grant", data, expected) + end + + test "permission-grant template with multiple inputs" do + data = %{"results" => [%{"permission" => %{"bundle" => "site", "name" => "foo"}, + "role" => %{"name" => "ops"}}, + %{"permission" => %{"bundle" => "site", "name" => "bar"}, + "role" => %{"name" => "dev"}}, + %{"permission" => %{"bundle" => "site", "name" => "baz"}, + "role" => %{"name" => "sec"}}]} + + expected = "Granted permission 'site:foo' to role 'ops'
    " <> + "Granted permission 'site:bar' to role 'dev'
    " <> + "Granted permission 'site:baz' to role 'sec'" + + assert_rendered_template(:hipchat, :embedded, "permission-grant", data, expected) + end + + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_info_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_info_test.exs new file mode 100644 index 00000000..f15a1d87 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_info_test.exs @@ -0,0 +1,32 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionInfoTest do + use Cog.TemplateCase + + test "permission-info template" do + data = %{"results" => [%{"id" => "123", "bundle" => "site", "name" => "foo"}]} + + expected = "ID: 123
    " <> + "Bundle: site
    " <> + "Name: foo" + + assert_rendered_template(:hipchat, :embedded, "permission-info", data, expected) + end + + test "permission-info template with multiple inputs" do + data = %{"results" => [%{"id" => "123", "bundle" => "foo_bundle", "name" => "foo"}, + %{"id" => "456", "bundle" => "bar_bundle", "name" => "bar"}, + %{"id" => "789", "bundle" => "baz_bundle", "name" => "baz"}]} + + expected = """ + + + + + +
    BundleNameID
    foo_bundlefoo123
    bar_bundlebar456
    baz_bundlebaz789
    + """ + + assert_rendered_template(:hipchat, :embedded, "permission-info", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_list_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_list_test.exs new file mode 100644 index 00000000..c742fd9a --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_list_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionListTest do + use Cog.TemplateCase + + test "permission-list template" do + data = %{"results" => [%{"bundle" => "site", "name" => "foo"}, + %{"bundle" => "site", "name" => "bar"}, + %{"bundle" => "site", "name" => "baz"}]} + + expected = """ + + + + + +
    BundleName
    sitefoo
    sitebar
    sitebaz
    + """ + + assert_rendered_template(:hipchat, :embedded, "permission-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/permission_revoke_test.exs b/test/cog/chat/hipchat/templates/embedded/permission_revoke_test.exs new file mode 100644 index 00000000..458f4aa7 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/permission_revoke_test.exs @@ -0,0 +1,28 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.PermissionRevokeTest do + use Cog.TemplateCase + + test "permission-revoke template" do + data = %{"results" => [%{"permission" => %{"bundle" => "site", + "name" => "foo"}, + "role" => %{"name" => "ops"}}]} + + expected = "Revoked permission 'site:foo' from role 'ops'" + assert_rendered_template(:hipchat, :embedded, "permission-revoke", data, expected) + end + + test "permission-revoke template with multiple inputs" do + data = %{"results" => [%{"permission" => %{"bundle" => "site", "name" => "foo"}, + "role" => %{"name" => "ops"}}, + %{"permission" => %{"bundle" => "site", "name" => "bar"}, + "role" => %{"name" => "dev"}}, + %{"permission" => %{"bundle" => "site", "name" => "baz"}, + "role" => %{"name" => "sec"}}]} + + expected = "Revoked permission 'site:foo' from role 'ops'
    " <> + "Revoked permission 'site:bar' from role 'dev'
    " <> + "Revoked permission 'site:baz' from role 'sec'" + + assert_rendered_template(:hipchat, :embedded, "permission-revoke", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_create_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_create_test.exs new file mode 100644 index 00000000..51f67bed --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupCreateTest do + use Cog.TemplateCase + + test "relay-group-create template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Created relay-group 'foo'" + assert_rendered_template(:hipchat, :embedded, "relay-group-create", data, expected) + end + + test "relay-group-create template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Created relay-group 'foo'
    " <> + "Created relay-group 'bar'
    " <> + "Created relay-group 'baz'" + + assert_rendered_template(:hipchat, :embedded, "relay-group-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_delete_test.exs new file mode 100644 index 00000000..0ee0ab99 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupDeleteTest do + use Cog.TemplateCase + + test "relay-group-delete template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Deleted relay-group 'foo'" + assert_rendered_template(:hipchat, :embedded, "relay-group-delete", data, expected) + end + + test "relay-group-delete template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Deleted relay-group 'foo'
    " <> + "Deleted relay-group 'bar'
    " <> + "Deleted relay-group 'baz'" + + assert_rendered_template(:hipchat, :embedded, "relay-group-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_info_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_info_test.exs new file mode 100644 index 00000000..048abf21 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_info_test.exs @@ -0,0 +1,49 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupInfoTest do + use Cog.TemplateCase + + test "relay-group-info template with one input" do + data = %{"results" => [%{"name" => "foo", + "created_at" => "some point in the past", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}]} + expected = "Name: foo" + + assert_rendered_template(:hipchat, :embedded, "relay-group-info", data, expected) + end + + test "relay-group-info template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", + "created_at" => "some point in the past", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}, + %{"name" => "bar", + "created_at" => "long long ago in a galaxy far away", + "relays" => [], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}, + %{"name" => "baz", + "created_at" => "right... NOW", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => []} + ]} + expected = """ + + + + + +
    Name
    foo
    bar
    baz
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-group-info", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_info_verbose_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_info_verbose_test.exs new file mode 100644 index 00000000..bb6e038c --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_info_verbose_test.exs @@ -0,0 +1,52 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupInfoVerboseTest do + use Cog.TemplateCase + + test "relay-group-info-verbose template with one input" do + data = %{"results" => [%{"name" => "foo", + "created_at" => "some point in the past", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}]} + expected = "Name: foo
    " <> + "Created At: some point in the past
    " <> + "Relays: my_relay, my_other_relay
    " <> + "Bundles: foo, bar, baz" + + assert_rendered_template(:hipchat, :embedded, "relay-group-info-verbose", data, expected) + end + + test "relay-group-info-verbose template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", + "created_at" => "some point in the past", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}, + %{"name" => "bar", + "created_at" => "long long ago in a galaxy far away", + "relays" => [], + "bundles" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]}, + %{"name" => "baz", + "created_at" => "right... NOW", + "relays" => [%{"name" => "my_relay"}, + %{"name" => "my_other_relay"}], + "bundles" => []} + ]} + expected = """ + + + + + +
    NameRelaysBundlesCreated At
    foomy_relay, my_other_relayfoo, bar, bazsome point in the past
    barfoo, bar, bazlong long ago in a galaxy far away
    bazmy_relay, my_other_relayright... NOW
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-group-info-verbose", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_list_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_list_test.exs new file mode 100644 index 00000000..9d4d3995 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_list_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupListTest do + use Cog.TemplateCase + + test "relay-group-list template" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + + expected = """ + + + + + +
    Name
    foo
    bar
    baz
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-group-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_rename_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_rename_test.exs new file mode 100644 index 00000000..8c84f444 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_rename_test.exs @@ -0,0 +1,25 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupRenameTest do + use Cog.TemplateCase + + test "relay-group-rename template" do + data = %{"results" => [%{"old_name" => "foo", + "relay_group" => %{"name" => "bar"}}]} + expected = "Renamed relay group 'foo' to 'bar'" + assert_rendered_template(:hipchat, :embedded, "relay-group-rename", data, expected) + end + + test "relay-group-rename template with multiple inputs" do + data = %{"results" => [%{"old_name" => "foo", + "relay_group" => %{"name" => "bar"}}, + %{"old_name" => "pinky", + "relay_group" => %{"name" => "brain"}}, + %{"old_name" => "mario", + "relay_group" => %{"name" => "luigi"}}]} + expected = "Renamed relay group 'foo' to 'bar'
    " <> + "Renamed relay group 'pinky' to 'brain'
    " <> + "Renamed relay group 'mario' to 'luigi'" + + assert_rendered_template(:hipchat, :embedded, "relay-group-rename", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_group_update_success_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_group_update_success_test.exs new file mode 100644 index 00000000..cd55e08a --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_group_update_success_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayGroupUpdateSuccessTest do + use Cog.TemplateCase + + test "relay-group-update-success template with one input" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Relay group 'foo' was successfully updated" + assert_rendered_template(:hipchat, :embedded, "relay-group-update-success", data, expected) + end + + test "relay-group-update-success template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Relay group 'foo' was successfully updated
    " <> + "Relay group 'bar' was successfully updated
    " <> + "Relay group 'baz' was successfully updated" + + assert_rendered_template(:hipchat, :embedded, "relay-group-update-success", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_info_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_info_test.exs new file mode 100644 index 00000000..fe9ed2fe --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_info_test.exs @@ -0,0 +1,91 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayInfoTest do + use Cog.TemplateCase + + test "relay-info template with one item with relay groups" do + data = %{"results" => [%{"id" => "123", + "name" => "relay_one", + "created_at" => "sometime", + "status" => "enabled", + "relay_groups" => [%{"name" => "prod"}, + %{"name" => "preprod"}, + %{"name" => "dev"}]}]} + expected = "ID: 123
    " <> + "Created: sometime
    " <> + "Name: relay_one
    " <> + "Status: enabled
    " <> + "Relay Groups: prod, preprod, dev" + + assert_rendered_template(:hipchat, :embedded, "relay-info", data, expected) + end + + test "relay-info template with one item without relay groups" do + data = %{"results" => [%{"id" => "123", + "name" => "relay_one", + "created_at" => "sometime", + "status" => "enabled"}]} + expected = "ID: 123
    " <> + "Created: sometime
    " <> + "Name: relay_one
    " <> + "Status: enabled" + + assert_rendered_template(:hipchat, :embedded, "relay-info", data, expected) + end + + test "relay-info with multiple results with relay groups" do + data = %{"results" => [%{"id" => "123", + "name" => "relay_one", + "created_at" => "yesterday", + "status" => "enabled", + "relay_groups" => []}, + %{"id" => "456", + "name" => "relay_two", + "created_at" => "3 days from now", + "status" => "disabled", + "relay_groups" => [%{"name" => "prod"}, + %{"name" => "preprod"}, + %{"name" => "dev"}]}, + %{"id" => "789", + "name" => "relay_three", + "created_at" => "the beginning of time itself", + "status" => "enabled", + "relay_groups" => [%{"name" => "prod"}]}]} + + expected = """ + + + + + +
    NameStatusIDCreatedRelay Groups
    relay_oneenabled123yesterday
    relay_twodisabled4563 days from nowprod, preprod, dev
    relay_threeenabled789the beginning of time itselfprod
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-info", data, expected) + end + + test "relay-info with multiple results without relay groups" do + data = %{"results" => [%{"id" => "123", + "name" => "relay_one", + "created_at" => "yesterday", + "status" => "enabled"}, + %{"id" => "456", + "name" => "relay_two", + "created_at" => "3 days from now", + "status" => "disabled"}, + %{"id" => "789", + "name" => "relay_three", + "created_at" => "the beginning of time itself", + "status" => "enabled"}]} + + expected = """ + + + + + +
    NameStatusIDCreated
    relay_oneenabled123yesterday
    relay_twodisabled4563 days from now
    relay_threeenabled789the beginning of time itself
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-info", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_list_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_list_test.exs new file mode 100644 index 00000000..d7820ed3 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_list_test.exs @@ -0,0 +1,50 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayListTest do + use Cog.TemplateCase + + test "relay-list template with relay groups" do + data = %{"results" => [%{"name" => "relay_one", + "status" => "enabled", + "relay_groups" => []}, + %{"name" => "relay_two", + "status" => "disabled", + "relay_groups" => [%{"name" => "prod"}, + %{"name" => "preprod"}, + %{"name" => "dev"}]}, + %{"name" => "relay_three", + "status" => "enabled", + "relay_groups" => [%{"name" => "prod"}]}]} + + expected = """ + + + + + +
    NameStatusRelay Groups
    relay_oneenabled
    relay_twodisabledprod, preprod, dev
    relay_threeenabledprod
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-list", data, expected) + end + + test "relay-list template without relay groups" do + data = %{"results" => [%{"name" => "relay_one", + "status" => "enabled"}, + %{"name" => "relay_two", + "status" => "disabled"}, + %{"name" => "relay_three", + "status" => "enabled"}]} + + expected = """ + + + + + +
    NameStatus
    relay_oneenabled
    relay_twodisabled
    relay_threeenabled
    + """ + + assert_rendered_template(:hipchat, :embedded, "relay-list", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/relay_update_test.exs b/test/cog/chat/hipchat/templates/embedded/relay_update_test.exs new file mode 100644 index 00000000..d365ce24 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/relay_update_test.exs @@ -0,0 +1,22 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RelayUpdateTest do + use Cog.TemplateCase + + test "relay-update with one input" do + data = %{"results" => [%{"name" => "relay_one"}]} + expected = "Relay 'relay_one' has been updated" + assert_rendered_template(:hipchat, :embedded, "relay-update", data, expected) + end + + test "relay-update template with multiple inupts" do + data = %{"results" => [%{"name" => "relay_one"}, + %{"name" => "relay_two"}, + %{"name" => "relay_three"}]} + expected = "Relay 'relay_one' has been updated
    " <> + "Relay 'relay_two' has been updated
    " <> + "Relay 'relay_three' has been updated" + + assert_rendered_template(:hipchat, :embedded, "relay-update", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_create_test.exs b/test/cog/chat/hipchat/templates/embedded/role_create_test.exs new file mode 100644 index 00000000..6eb65ade --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleCreateTest do + use Cog.TemplateCase + + test "role-create template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Created role 'foo'" + assert_rendered_template(:hipchat, :embedded, "role-create", data, expected) + end + + test "role-create template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Created role 'foo'
    " <> + "Created role 'bar'
    " <> + "Created role 'baz'" + + assert_rendered_template(:hipchat, :embedded, "role-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/role_delete_test.exs new file mode 100644 index 00000000..ac95dcf1 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleDeleteTest do + use Cog.TemplateCase + + test "role-delete template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Deleted role 'foo'" + assert_rendered_template(:hipchat, :embedded, "role-delete", data, expected) + end + + test "role-delete template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Deleted role 'foo'
    " <> + "Deleted role 'bar'
    " <> + "Deleted role 'baz'" + + assert_rendered_template(:hipchat, :embedded, "role-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_grant_test.exs b/test/cog/chat/hipchat/templates/embedded/role_grant_test.exs new file mode 100644 index 00000000..7ddcb2f8 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_grant_test.exs @@ -0,0 +1,29 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleGrantTest do + use Cog.TemplateCase + + test "role-grant template" do + data = %{"results" => [%{"role" => %{"name" => "admin"}, + "group" => %{"name" => "ops"}}]} + + expected = "Granted role 'admin' to group 'ops'" + assert_rendered_template(:hipchat, :embedded, "role-grant", data, expected) + end + + test "role-grant template with multiple inputs" do + data = %{"results" => [%{"role" => %{"name" => "cog-admin"}, + "group" => %{"name" => "ops"}}, + %{"role" => %{"name" => "aws-admin"}, + "group" => %{"name" => "developers"}}, + %{"role" => %{"name" => "heroku-admin"}, + "group" => %{"name" => "developers"}}]} + + expected = "Granted role 'cog-admin' to group 'ops'
    " <> + "Granted role 'aws-admin' to group 'developers'
    " <> + "Granted role 'heroku-admin' to group 'developers'" + + assert_rendered_template(:hipchat, :embedded, "role-grant", data, expected) + end + + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_info_test.exs b/test/cog/chat/hipchat/templates/embedded/role_info_test.exs new file mode 100644 index 00000000..6cda8d6a --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_info_test.exs @@ -0,0 +1,43 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleInfoTest do + use Cog.TemplateCase + + test "role-info template with one input" do + data = %{"results" => [%{"id" => "123", + "name" => "foo", + "permissions" => [%{"bundle" => "site", "name" => "foo"}]}]} + + expected = "ID: 123
    " <> + "Name: foo
    " <> + "Permissions: site:foo" + + assert_rendered_template(:hipchat, :embedded, "role-info", data, expected) + end + + test "role-info template with multiple inputs" do + data = %{"results" => [%{"id" => "123", + "name" => "foo", + "permissions" => [%{"bundle" => "site", "name" => "foo"}]}, + %{"id" => "456", + "name" => "bar", + "permissions" => [%{"bundle" => "site", "name" => "foo"}, + %{"bundle" => "operable", "name" => "blah"}]}, + %{"id" => "789", + "name" => "baz", + "permissions" => [%{"bundle" => "site", "name" => "foo"}]} + + ]} + + expected = """ + + + + + +
    NameIDPermissions
    foo123site:foo
    bar456site:foo, operable:blah
    baz789site:foo
    + """ + + assert_rendered_template(:hipchat, :embedded, "role-info", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_list_test.exs b/test/cog/chat/hipchat/templates/embedded/role_list_test.exs new file mode 100644 index 00000000..726f8af9 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_list_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleListTest do + use Cog.TemplateCase + + test "role-list template" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + + expected = """ + + + + + +
    Name
    foo
    bar
    baz
    + """ + + assert_rendered_template(:hipchat, :embedded, "role-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_rename_test.exs b/test/cog/chat/hipchat/templates/embedded/role_rename_test.exs new file mode 100644 index 00000000..8dcfcb47 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_rename_test.exs @@ -0,0 +1,25 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleRenameTest do + use Cog.TemplateCase + + test "role-rename template" do + data = %{"results" => [%{"old_name" => "foo", + "name" => "bar"}]} + expected = "Renamed role 'foo' to 'bar'" + assert_rendered_template(:hipchat, :embedded, "role-rename", data, expected) + end + + test "role-rename template with multiple inputs" do + data = %{"results" => [%{"old_name" => "foo", + "name" => "bar"}, + %{"old_name" => "pinky", + "name" => "brain"}, + %{"old_name" => "mario", + "name" => "luigi"}]} + expected = "Renamed role 'foo' to 'bar'
    " <> + "Renamed role 'pinky' to 'brain'
    " <> + "Renamed role 'mario' to 'luigi'" + + assert_rendered_template(:hipchat, :embedded, "role-rename", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/role_revoke_test.exs b/test/cog/chat/hipchat/templates/embedded/role_revoke_test.exs new file mode 100644 index 00000000..b1dbea7d --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/role_revoke_test.exs @@ -0,0 +1,29 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RoleRevokeTest do + use Cog.TemplateCase + + test "role-revoke template" do + data = %{"results" => [%{"role" => %{"name" => "admin"}, + "group" => %{"name" => "ops"}}]} + + expected = "Revoked role 'admin' from group 'ops'" + assert_rendered_template(:hipchat, :embedded, "role-revoke", data, expected) + end + + test "role-revoke template with multiple inputs" do + data = %{"results" => [%{"role" => %{"name" => "cog-admin"}, + "group" => %{"name" => "ops"}}, + %{"role" => %{"name" => "aws-admin"}, + "group" => %{"name" => "developers"}}, + %{"role" => %{"name" => "heroku-admin"}, + "group" => %{"name" => "developers"}}]} + + expected = "Revoked role 'cog-admin' from group 'ops'
    " <> + "Revoked role 'aws-admin' from group 'developers'
    " <> + "Revoked role 'heroku-admin' from group 'developers'" + + assert_rendered_template(:hipchat, :embedded, "role-revoke", data, expected) + end + + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/rule_create_test.exs b/test/cog/chat/hipchat/templates/embedded/rule_create_test.exs new file mode 100644 index 00000000..cec54a41 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/rule_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RuleCreateTest do + use Cog.TemplateCase + + test "rule-create template" do + data = %{"results" => [%{"id" => "12345"}]} + expected = "Created rule '12345'" + assert_rendered_template(:hipchat, :embedded, "rule-create", data, expected) + end + + test "rule-create template with multiple inputs" do + data = %{"results" => [%{"id" => "12345"}, + %{"id" => "67890"}, + %{"id" => "11111"}]} + expected = "Created rule '12345'
    " <> + "Created rule '67890'
    " <> + "Created rule '11111'" + + assert_rendered_template(:hipchat, :embedded, "rule-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/rule_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/rule_delete_test.exs new file mode 100644 index 00000000..70e517ec --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/rule_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RuleDeleteTest do + use Cog.TemplateCase + + test "rule-delete template" do + data = %{"results" => [%{"id" => "12345"}]} + expected = "Deleted rule '12345'" + assert_rendered_template(:hipchat, :embedded, "rule-delete", data, expected) + end + + test "rule-delete template with multiple inputs" do + data = %{"results" => [%{"id" => "12345"}, + %{"id" => "67890"}, + %{"id" => "11111"}]} + expected = "Deleted rule '12345'
    " <> + "Deleted rule '67890'
    " <> + "Deleted rule '11111'" + + assert_rendered_template(:hipchat, :embedded, "rule-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/rule_info_test.exs b/test/cog/chat/hipchat/templates/embedded/rule_info_test.exs new file mode 100644 index 00000000..f2134b58 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/rule_info_test.exs @@ -0,0 +1,38 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RuleInfoTest do + use Cog.TemplateCase + + test "rule-info template" do + data = %{"results" => [%{"command_name" => "foo:foo", + "rule" => "when command is foo:foo allow", + "id" => "123"}]} + expected = "ID: 123
    " <> + "Command: foo:foo
    " <> + "Rule: when command is foo:foo allow" + + assert_rendered_template(:hipchat, :embedded, "rule-info", data, expected) + end + + test "rule-info template with multiple rules" do + data = %{"results" => [%{"command_name" => "foo:foo", + "rule" => "when command is foo:foo allow", + "id" => "123"}, + %{"command_name" => "foo:bar", + "rule" => "when command is foo:bar allow", + "id" => "456"}, + %{"command_name" => "foo:baz", + "rule" => "when command is foo:baz allow", + "id" => "789"}]} + expected = """ + + + + + +
    CommandRuleID
    foo:foowhen command is foo:foo allow123
    foo:barwhen command is foo:bar allow456
    foo:bazwhen command is foo:baz allow789
    + """ + + assert_rendered_template(:hipchat, :embedded, "rule-info", data, expected) + end + + +end diff --git a/test/cog/chat/hipchat/templates/embedded/rule_list_test.exs b/test/cog/chat/hipchat/templates/embedded/rule_list_test.exs new file mode 100644 index 00000000..0e542f83 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/rule_list_test.exs @@ -0,0 +1,26 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.RuleListTest do + use Cog.TemplateCase + + test "rule-list template" do + data = %{"results" => [%{"command" => "foo:foo", + "rule" => "when command is foo:foo allow", + "id" => "123"}, + %{"command" => "foo:bar", + "rule" => "when command is foo:bar allow", + "id" => "456"}, + %{"command" => "foo:baz", + "rule" => "when command is foo:baz allow", + "id" => "789"}]} + expected = """ + + + + + +
    CommandRuleID
    foo:foowhen command is foo:foo allow123
    foo:barwhen command is foo:bar allow456
    foo:bazwhen command is foo:baz allow789
    + """ + + assert_rendered_template(:hipchat, :embedded, "rule-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_create_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_create_test.exs new file mode 100644 index 00000000..61daf9a1 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerCreateTest do + use Cog.TemplateCase + + test "trigger-create template" do + data = %{"results" => [%{"id" => "12345"}]} + expected = "Created trigger '12345'" + assert_rendered_template(:hipchat, :embedded, "trigger-create", data, expected) + end + + test "trigger-create template with multiple inputs" do + data = %{"results" => [%{"id" => "12345"}, + %{"id" => "67890"}, + %{"id" => "11111"}]} + expected = "Created trigger '12345'
    " <> + "Created trigger '67890'
    " <> + "Created trigger '11111'" + + assert_rendered_template(:hipchat, :embedded, "trigger-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_delete_test.exs new file mode 100644 index 00000000..4186d084 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerDeleteTest do + use Cog.TemplateCase + + test "trigger-delete template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Deleted trigger 'foo'" + assert_rendered_template(:hipchat, :embedded, "trigger-delete", data, expected) + end + + test "trigger-delete template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Deleted trigger 'foo'
    " <> + "Deleted trigger 'bar'
    " <> + "Deleted trigger 'baz'" + + assert_rendered_template(:hipchat, :embedded, "trigger-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_disable_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_disable_test.exs new file mode 100644 index 00000000..ca313250 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_disable_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerDisableTest do + use Cog.TemplateCase + + test "trigger-disable template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Disabled trigger 'foo'" + assert_rendered_template(:hipchat, :embedded, "trigger-disable", data, expected) + end + + test "trigger-disable template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Disabled trigger 'foo'
    " <> + "Disabled trigger 'bar'
    " <> + "Disabled trigger 'baz'" + + assert_rendered_template(:hipchat, :embedded, "trigger-disable", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_enable_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_enable_test.exs new file mode 100644 index 00000000..84eb8b33 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_enable_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerEnableTest do + use Cog.TemplateCase + + test "trigger-enable template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Enabled trigger 'foo'" + assert_rendered_template(:hipchat, :embedded, "trigger-enable", data, expected) + end + + test "trigger-enable template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Enabled trigger 'foo'
    " <> + "Enabled trigger 'bar'
    " <> + "Enabled trigger 'baz'" + + assert_rendered_template(:hipchat, :embedded, "trigger-enable", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_info_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_info_test.exs new file mode 100644 index 00000000..fb99d3cc --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_info_test.exs @@ -0,0 +1,71 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerInfoTest do + use Cog.TemplateCase + + test "trigger-info template with one input" do + data = %{"results" => [%{"id" => "abc123", + "name" => "test_trigger", + "description" => "Tests things!", + "enabled" => true, + "pipeline" => "echo 'Something just happened'", + "as_user" => "bobby_tables", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_stuff"}]} + expected = "ID: abc123
    " <> + "Name: test_trigger
    " <> + "Description: Tests things!
    " <> + "Enabled?: true
    " <> + "Pipeline: echo 'Something just happened'
    " <> + "As User: bobby_tables
    " <> + "Timeout (sec): 30
    " <> + "Invocation URL: https://cog.mycompany.com/invoke_stuff" + + + assert_rendered_template(:hipchat, :embedded, "trigger-info", data, expected) + end + + test "handles null description, user" do + data = %{"results" => [%{"id" => "abc123", + "name" => "test_trigger", + "enabled" => true, + "pipeline" => "echo 'Something just happened'", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_stuff"}]} + expected = "ID: abc123
    " <> + "Name: test_trigger
    " <> + "Description:
    " <> + "Enabled?: true
    " <> + "Pipeline: echo 'Something just happened'
    " <> + "As User:
    " <> + "Timeout (sec): 30
    " <> + "Invocation URL: https://cog.mycompany.com/invoke_stuff" + + assert_rendered_template(:hipchat, :embedded, "trigger-info", data, expected) + end + + test "multiple inputs" do + data = %{"results" => [%{"id" => "abc123", + "name" => "test_trigger", + "description" => "Tests things!", + "enabled" => true, + "pipeline" => "echo 'Something just happened'", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_stuff"}, + %{"id" => "abc456", + "name" => "test_trigger_2", + "enabled" => false, + "pipeline" => "echo 'Something else just happened'", + "as_user" => "bobby_tables", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_other_stuff"}]} + expected = """ + + + + +
    IDNameDescriptionEnabled?PipelineAs UserTimeoutInvocation URL
    abc123test_triggerTests things!trueecho 'Something just happened'30https://cog.mycompany.com/invoke_stuff
    abc456test_trigger_2falseecho 'Something else just happened'bobby_tables30https://cog.mycompany.com/invoke_other_stuff
    + """ + + assert_rendered_template(:hipchat, :embedded, "trigger-info", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/trigger_list_test.exs b/test/cog/chat/hipchat/templates/embedded/trigger_list_test.exs new file mode 100644 index 00000000..d582abb0 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/trigger_list_test.exs @@ -0,0 +1,59 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.TriggerListTest do + use Cog.TemplateCase + + test "trigger-list template" do + data = %{"results" => [%{"id" => "abc123", + "name" => "test_trigger", + "description" => "Tests things!", + "enabled" => true, + "pipeline" => "echo 'Something just happened'", + "as_user" => "bobby_tables", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_stuff"}, + %{"id" => "abc456", + "name" => "test_trigger_2", + "description" => "Tests more things!", + "enabled" => false, + "pipeline" => "echo 'Something else just happened'", + "as_user" => "bobby_tables", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_other_stuff"}]} + expected = """ + + + + +
    IDNameDescriptionEnabled?PipelineAs UserTimeoutInvocation URL
    abc123test_triggerTests things!trueecho 'Something just happened'bobby_tables30https://cog.mycompany.com/invoke_stuff
    abc456test_trigger_2Tests more things!falseecho 'Something else just happened'bobby_tables30https://cog.mycompany.com/invoke_other_stuff
    + """ + + assert_rendered_template(:hipchat, :embedded, "trigger-list", data, expected) + end + + test "handles null description, user" do + data = %{"results" => [%{"id" => "abc123", + "name" => "test_trigger", + "description" => "Tests things!", + "enabled" => true, + "pipeline" => "echo 'Something just happened'", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_stuff"}, + %{"id" => "abc456", + "name" => "test_trigger_2", + "enabled" => false, + "pipeline" => "echo 'Something else just happened'", + "as_user" => "bobby_tables", + "timeout_sec" => 30, + "invocation_url" => "https://cog.mycompany.com/invoke_other_stuff"}]} + expected = """ + + + + +
    IDNameDescriptionEnabled?PipelineAs UserTimeoutInvocation URL
    abc123test_triggerTests things!trueecho 'Something just happened'30https://cog.mycompany.com/invoke_stuff
    abc456test_trigger_2falseecho 'Something else just happened'bobby_tables30https://cog.mycompany.com/invoke_other_stuff
    + """ + + assert_rendered_template(:hipchat, :embedded, "trigger-list", data, expected) + + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/usage_test.exs b/test/cog/chat/hipchat/templates/embedded/usage_test.exs new file mode 100644 index 00000000..3acd57bb --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/usage_test.exs @@ -0,0 +1,20 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UsageTest do + use Cog.TemplateCase + + test "usage template - with error" do + data = %{"results" => [%{"error" => "Oopsie... something went wrong", + "usage" => "Do this instead..."}]} + expected = "Oopsie... something went wrong

    " <> + "Do this instead..." + + assert_rendered_template(:hipchat, :embedded, "usage", data, expected) + end + + test "usage template - without error" do + data = %{"results" => [%{"usage" => "Do this instead..."}]} + expected = "Do this instead..." + + assert_rendered_template(:hipchat, :embedded, "usage", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_attach_handle_test.exs b/test/cog/chat/hipchat/templates/embedded/user_attach_handle_test.exs new file mode 100644 index 00000000..a38e47c7 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_attach_handle_test.exs @@ -0,0 +1,29 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserAttachHandleTest do + use Cog.TemplateCase + + test "user-attach-handle template" do + data = %{"results" => [%{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "cog", + "username" => "cog"}]} + expected = "Attached HipChat handle @cog to Cog user 'cog'" + assert_rendered_template(:hipchat, :embedded, "user-attach-handle", data, expected) + end + + test "user-attach-handle template with multiple inputs" do + data = %{"results" => [%{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "cog", + "username" => "cog"}, + %{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "sprocket", + "username" => "sprocket"}, + %{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "gear", + "username" => "herman"}]} + expected = "Attached HipChat handle @cog to Cog user 'cog'
    " <> + "Attached HipChat handle @sprocket to Cog user 'sprocket'
    " <> + "Attached HipChat handle @gear to Cog user 'herman'" + + assert_rendered_template(:hipchat, :embedded, "user-attach-handle", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_detach_handle_test.exs b/test/cog/chat/hipchat/templates/embedded/user_detach_handle_test.exs new file mode 100644 index 00000000..28707f09 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_detach_handle_test.exs @@ -0,0 +1,25 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserDetachHandleTest do + use Cog.TemplateCase + + test "user-detach-handle template" do + data = %{"results" => [%{"chat_provider" => %{"name" => "HipChat"}, + "username" => "cog"}]} + expected = "Removed HipChat handle from Cog user 'cog'" + assert_rendered_template(:hipchat, :embedded, "user-detach-handle", data, expected) + end + + test "user-detach-handle template with multiple inputs" do + data = %{"results" => [%{"chat_provider" => %{"name" => "HipChat"}, + "username" => "cog"}, + %{"chat_provider" => %{"name" => "HipChat"}, + "username" => "sprocket"}, + %{"chat_provider" => %{"name" => "HipChat"}, + "username" => "herman"}]} + expected = "Removed HipChat handle from Cog user 'cog'
    " <> + "Removed HipChat handle from Cog user 'sprocket'
    " <> + "Removed HipChat handle from Cog user 'herman'" + + assert_rendered_template(:hipchat, :embedded, "user-detach-handle", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_create_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_create_test.exs new file mode 100644 index 00000000..16c9533c --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_create_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupCreateTest do + use Cog.TemplateCase + + test "user-group-create template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Created user group 'foo'" + assert_rendered_template(:hipchat, :embedded, "user-group-create", data, expected) + end + + test "user-group-create template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Created user group 'foo'
    " <> + "Created user group 'bar'
    " <> + "Created user group 'baz'" + + assert_rendered_template(:hipchat, :embedded, "user-group-create", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_delete_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_delete_test.exs new file mode 100644 index 00000000..557eb6e1 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_delete_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupDeleteTest do + use Cog.TemplateCase + + test "user-group-delete template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "Deleted user group 'foo'" + assert_rendered_template(:hipchat, :embedded, "user-group-delete", data, expected) + end + + test "user-group-delete template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "Deleted user group 'foo'
    " <> + "Deleted user group 'bar'
    " <> + "Deleted user group 'baz'" + + assert_rendered_template(:hipchat, :embedded, "user-group-delete", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_info_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_info_test.exs new file mode 100644 index 00000000..811a8a30 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_info_test.exs @@ -0,0 +1,54 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupInfoTest do + use Cog.TemplateCase + + test "user-group-info template" do + data = %{"results" => [%{"name" => "foo", + "id" => "123", + "roles" => [%{"name" => "heroku-admin"}, + %{"name" => "aws-admin"}], + "members" => [%{"username" => "larry"}, + %{"username" => "moe"}, + %{"username" => "curly"}]}]} + + expected = "Name: foo
    " <> + "ID: 123
    " <> + "Roles: heroku-admin, aws-admin
    " <> + "Members: larry, moe, curly" + + assert_rendered_template(:hipchat, :embedded, "user-group-info", data, expected) + end + + test "user-group-info template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", + "id" => "123", + "roles" => [%{"name" => "heroku-admin"}, + %{"name" => "aws-admin"}], + "members" => [%{"username" => "larry"}, + %{"username" => "moe"}, + %{"username" => "curly"}]}, + %{"name" => "bar", + "id" => "456", + "roles" => [%{"name" => "bar-admin"}], + "members" => [%{"username" => "sterling"}, + %{"username" => "lana"}, + %{"username" => "pam"}]}, + %{"name" => "baz", + "id" => "789", + "roles" => [%{"name" => "baz-admin"}], + "members" => [%{"username" => "tina"}, + %{"username" => "gene"}, + %{"username" => "louise"}]}]} + + expected = """ + + + + + +
    NameIDRolesMembers
    foo123heroku-admin, aws-adminlarry, moe, curly
    bar456bar-adminsterling, lana, pam
    baz789baz-admintina, gene, louise
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-group-info", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_list_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_list_test.exs new file mode 100644 index 00000000..c1602200 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_list_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupListTest do + use Cog.TemplateCase + + test "user-group-list template" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + + expected = """ + + + + + +
    Name
    foo
    bar
    baz
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-group-list", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_list_verbose_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_list_verbose_test.exs new file mode 100644 index 00000000..1876f9c8 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_list_verbose_test.exs @@ -0,0 +1,37 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupListVerboseTest do + use Cog.TemplateCase + + test "user-group-list-verbose template with multiple inputs" do + data = %{"results" => [%{"name" => "foo", + "id" => "123", + "roles" => [%{"name" => "heroku-admin"}, + %{"name" => "aws-admin"}], + "members" => [%{"username" => "larry"}, + %{"username" => "moe"}, + %{"username" => "curly"}]}, + %{"name" => "bar", + "id" => "456", + "roles" => [%{"name" => "bar-admin"}], + "members" => [%{"username" => "sterling"}, + %{"username" => "lana"}, + %{"username" => "pam"}]}, + %{"name" => "baz", + "id" => "789", + "roles" => [%{"name" => "baz-admin"}], + "members" => [%{"username" => "tina"}, + %{"username" => "gene"}, + %{"username" => "louise"}]}]} + + expected = """ + + + + + +
    NameIDRolesMembers
    foo123heroku-admin, aws-adminlarry, moe, curly
    bar456bar-adminsterling, lana, pam
    baz789baz-admintina, gene, louise
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-group-list-verbose", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_group_update_success_test.exs b/test/cog/chat/hipchat/templates/embedded/user_group_update_success_test.exs new file mode 100644 index 00000000..59f0ff58 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_group_update_success_test.exs @@ -0,0 +1,21 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserGroupUpdateSuccessTest do + use Cog.TemplateCase + + test "user-group-update-success template" do + data = %{"results" => [%{"name" => "foo"}]} + expected = "User group 'foo' was successfully updated" + assert_rendered_template(:hipchat, :embedded, "user-group-update-success", data, expected) + end + + test "user-group-update-success template with multiple inputs" do + data = %{"results" => [%{"name" => "foo"}, + %{"name" => "bar"}, + %{"name" => "baz"}]} + expected = "User group 'foo' was successfully updated
    " <> + "User group 'bar' was successfully updated
    " <> + "User group 'baz' was successfully updated" + + assert_rendered_template(:hipchat, :embedded, "user-group-update-success", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_info_test.exs b/test/cog/chat/hipchat/templates/embedded/user_info_test.exs new file mode 100644 index 00000000..e7074997 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_info_test.exs @@ -0,0 +1,55 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserInfoTest do + use Cog.TemplateCase + + test "user-info template with one input" do + data = %{"results" => [%{"username" => "cog", + "first_name" => "Cog", + "last_name" => "McCog", + "email_address" => "cog@example.com", + "groups" => [%{"name" => "dev"}, + %{"name" => "ops"}], + "chat_handles" => [%{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "the_cog"}]}]} + + expected = "Username: cog
    " <> + "First Name: Cog
    " <> + "Last Name: McCog
    " <> + "Email: cog@example.com
    " <> + "Groups: dev, ops
    " <> + "Handles: the_cog (HipChat)" + + assert_rendered_template(:hipchat, :embedded, "user-info", data, expected) + end + + test "user-info template with multiple inputs" do + data = %{"results" => [%{"username" => "cog", + "first_name" => "Cog", + "last_name" => "McCog", + "email_address" => "cog@example.com", + "groups" => [%{"name" => "dev"}, + %{"name" => "ops"}], + "chat_handles" => [%{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "the_cog"}]}, + + %{"username" => "sprocket", + "first_name" => "Sprocket", + "last_name" => "McSprocket", + "email_address" => "sprocket@example.com", + "groups" => [%{"name" => "sec"}, + %{"name" => "test"}], + "chat_handles" => [%{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "sprocket"}, + %{"chat_provider" => %{"name" => "HipChat"}, + "handle" => "SprocketMcSprocket"}]} + ]} + expected = """ + + + + +
    UsernameFirst NameLast NameEmailGroupsHandles
    cogCogMcCogcog@example.comdev, opsthe_cog (HipChat)
    sprocketSprocketMcSprocketsprocket@example.comsec, testsprocket (HipChat), SprocketMcSprocket (HipChat)
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-info", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_list_handles_test.exs b/test/cog/chat/hipchat/templates/embedded/user_list_handles_test.exs new file mode 100644 index 00000000..ec725f14 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_list_handles_test.exs @@ -0,0 +1,23 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserListHandlesTest do + use Cog.TemplateCase + + test "user-list-handles template" do + data = %{"results" => [%{"username" => "cog", + "handle" => "cog"}, + %{"username" => "sprocket", + "handle" => "spacely"}, + %{"username" => "chetops", + "handle" => "ChetOps"}]} + expected = """ + + + + + +
    UsernameHandle
    cog@cog
    sprocket@spacely
    chetops@ChetOps
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-list-handles", data, expected) + end + +end diff --git a/test/cog/chat/hipchat/templates/embedded/user_list_test.exs b/test/cog/chat/hipchat/templates/embedded/user_list_test.exs new file mode 100644 index 00000000..a4a01745 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/user_list_test.exs @@ -0,0 +1,49 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.UserListTest do + use Cog.TemplateCase + + test "user-list template" do + data = %{"results" => [%{"username" => "cog", + "first_name" => "Cog", + "last_name" => "McCog", + "email_address" => "cog@example.com"}, + %{"username" => "sprocket", + "first_name" => "Sprocket", + "last_name" => "McCog", + "email_address" => "sprocket@example.com"}, + %{"username" => "chetops", + "first_name" => "Chet", + "last_name" => "Ops", + "email_address" => "chetops@example.com"}]} + expected = """ + + + + + +
    UsernameFirst NameLast NameEmail
    cogCogMcCogcog@example.com
    sprocketSprocketMcCogsprocket@example.com
    chetopsChetOpschetops@example.com
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-list", data, expected) + end + + test "handle when names aren't specified" do + data = %{"results" => [%{"username" => "cog", + "first_name" => "Cog", + "email_address" => "cog@example.com"}, + %{"username" => "sprocket", + "last_name" => "McCog", + "email_address" => "sprocket@example.com"}, + %{"username" => "chetops", + "email_address" => "chetops@example.com"}]} + expected = """ + + + + + +
    UsernameFirst NameLast NameEmail
    cogCogcog@example.com
    sprocketMcCogsprocket@example.com
    chetopschetops@example.com
    + """ + + assert_rendered_template(:hipchat, :embedded, "user-list", data, expected) + end +end diff --git a/test/cog/chat/hipchat/templates/embedded/which_test.exs b/test/cog/chat/hipchat/templates/embedded/which_test.exs new file mode 100644 index 00000000..27c8bb73 --- /dev/null +++ b/test/cog/chat/hipchat/templates/embedded/which_test.exs @@ -0,0 +1,41 @@ +defmodule Cog.Chat.HipChat.Templates.Embedded.WhichTest do + use Cog.TemplateCase + + test "which template - alias result" do + data = %{"results" => [%{"type" => "alias", + "scope" => "site", + "name" => "foo", + "pipeline" => "echo 'foo'"}]} + expected = "alias - site:foo ->
    echo 'foo'
    " + assert_rendered_template(:hipchat, :embedded, "which", data, expected) + end + + test "which template - command result" do + data = %{"results" => [%{"type" => "command", + "scope" => "operable", + "name" => "alias"}]} + expected = "command - operable:alias" + assert_rendered_template(:hipchat, :embedded, "which", data, expected) + end + + test "which template - multiple inputs" do + data = %{"results" => [%{"type" => "alias", + "scope" => "site", + "name" => "foo", + "pipeline" => "echo 'foo'"}, + %{"type" => "command", + "scope" => "operable", + "name" => "alias"}, + %{"type" => "alias", + "scope" => "user", + "name" => "bar", + "pipeline" => "echo 'bar'"}]} + + expected = "alias - site:foo ->
    echo 'foo'

    " <> + "command - operable:alias
    " <> + "alias - user:bar ->
    echo 'bar'
    " + + assert_rendered_template(:hipchat, :embedded, "which", data, expected) + end + +end diff --git a/test/integration/alias_execution_test.exs b/test/commands/alias_execution_test.exs similarity index 96% rename from test/integration/alias_execution_test.exs rename to test/commands/alias_execution_test.exs index 8f6092d7..33c8736e 100644 --- a/test/integration/alias_execution_test.exs +++ b/test/commands/alias_execution_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.AliasExecutionTest do +defmodule Cog.Test.Commands.AliasExecutionTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/alias_test.exs b/test/commands/alias_test.exs similarity index 99% rename from test/integration/commands/alias_test.exs rename to test/commands/alias_test.exs index e5346a31..13c69d5d 100644 --- a/test/integration/commands/alias_test.exs +++ b/test/commands/alias_test.exs @@ -1,5 +1,8 @@ -defmodule Integration.Commands.AliasTest do +defmodule Cog.Test.Commands.AliasTest do use Cog.AdapterCase, adapter: "test" + + @moduletag :skip + alias Cog.Models.UserCommandAlias alias Cog.Models.SiteCommandAlias alias Cog.Repo diff --git a/test/integration/commands/bundle_test.exs b/test/commands/bundle_test.exs similarity index 97% rename from test/integration/commands/bundle_test.exs rename to test/commands/bundle_test.exs index c086909e..831b724c 100644 --- a/test/integration/commands/bundle_test.exs +++ b/test/commands/bundle_test.exs @@ -1,5 +1,8 @@ -defmodule Integration.Commands.BundleTest do +defmodule Cog.Test.Commands.BundleTest do use Cog.AdapterCase, adapter: "test" + + @moduletag :skip + alias Cog.Repository.Bundles alias Cog.Support.ModelUtilities diff --git a/test/integration/command_test.exs b/test/commands/command_test.exs similarity index 99% rename from test/integration/command_test.exs rename to test/commands/command_test.exs index ef532a72..5a96a50d 100644 --- a/test/integration/command_test.exs +++ b/test/commands/command_test.exs @@ -1,6 +1,8 @@ defmodule Integration.CommandTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/echo_test.exs b/test/commands/echo_test.exs similarity index 90% rename from test/integration/commands/echo_test.exs rename to test/commands/echo_test.exs index 2d009e3d..8e11a3f8 100644 --- a/test/integration/commands/echo_test.exs +++ b/test/commands/echo_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.EchoTest do +defmodule Cog.Test.Commands.EchoTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/filter_test.exs b/test/commands/filter_test.exs similarity index 95% rename from test/integration/commands/filter_test.exs rename to test/commands/filter_test.exs index ef56280d..583ede1a 100644 --- a/test/integration/commands/filter_test.exs +++ b/test/commands/filter_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.FilterTest do +defmodule Cog.Test.Commands.FilterTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/group_test.exs b/test/commands/group_test.exs similarity index 99% rename from test/integration/commands/group_test.exs rename to test/commands/group_test.exs index 73f7250c..550b47f1 100644 --- a/test/integration/commands/group_test.exs +++ b/test/commands/group_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.GroupTest do +defmodule Cog.Test.Commands.GroupTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + alias Cog.Models.Group alias Cog.Repository.Groups diff --git a/test/integration/commands/help_test.exs b/test/commands/help_test.exs similarity index 93% rename from test/integration/commands/help_test.exs rename to test/commands/help_test.exs index 39b8d20f..a9aa5015 100644 --- a/test/integration/commands/help_test.exs +++ b/test/commands/help_test.exs @@ -1,5 +1,8 @@ -defmodule Integration.Commands.HelpTest do +defmodule Cog.Test.Commands.HelpTest do use Cog.AdapterCase, adapter: "test" + + @moduletag :skip + alias Cog.Support.ModelUtilities setup do diff --git a/test/integration/commands/max_test.exs b/test/commands/max_test.exs similarity index 98% rename from test/integration/commands/max_test.exs rename to test/commands/max_test.exs index 6fad3b46..545e5d20 100644 --- a/test/integration/commands/max_test.exs +++ b/test/commands/max_test.exs @@ -1,6 +1,8 @@ defmodule Integration.MaxTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("jfrost", first_name: "Jim", last_name: "Frost") |> with_chat_handle_for("test") diff --git a/test/integration/commands/min_test.exs b/test/commands/min_test.exs similarity index 94% rename from test/integration/commands/min_test.exs rename to test/commands/min_test.exs index 90c9e216..30537557 100644 --- a/test/integration/commands/min_test.exs +++ b/test/commands/min_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.MinTest do +defmodule Cog.Test.Commands.MinTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/permission_test.exs b/test/commands/permission_test.exs similarity index 99% rename from test/integration/commands/permission_test.exs rename to test/commands/permission_test.exs index 20d2f5a8..2dadb0fa 100644 --- a/test/integration/commands/permission_test.exs +++ b/test/commands/permission_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.PermissionTest do +defmodule Cog.Test.Commands.PermissionTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + alias Cog.Repository.Permissions import DatabaseAssertions, only: [assert_permission_is_granted: 2, diff --git a/test/integration/redirect_test.exs b/test/commands/redirect_test.exs similarity index 99% rename from test/integration/redirect_test.exs rename to test/commands/redirect_test.exs index ab2ee478..5fe9d9ef 100644 --- a/test/integration/redirect_test.exs +++ b/test/commands/redirect_test.exs @@ -1,5 +1,8 @@ defmodule Integration.RedirectTest do use Cog.AdapterCase, adapter: "test" + + @moduletag :skip + alias Cog.Snoop require Logger diff --git a/test/integration/commands/relay_group_test.exs b/test/commands/relay_group_test.exs similarity index 98% rename from test/integration/commands/relay_group_test.exs rename to test/commands/relay_group_test.exs index ea6f073c..0cb49a46 100644 --- a/test/integration/commands/relay_group_test.exs +++ b/test/commands/relay_group_test.exs @@ -1,5 +1,8 @@ -defmodule Integration.Commands.RelayGroupTest do +defmodule Cog.Test.Commands.RelayGroupTest do use Cog.AdapterCase, adapter: "test" + + @moduletag :skip + alias Cog.Support.ModelUtilities alias Cog.Models.RelayGroup alias Cog.Repo diff --git a/test/integration/commands/relay_test.exs b/test/commands/relay_test.exs similarity index 98% rename from test/integration/commands/relay_test.exs rename to test/commands/relay_test.exs index c1a6d338..55484e9c 100644 --- a/test/integration/commands/relay_test.exs +++ b/test/commands/relay_test.exs @@ -1,9 +1,11 @@ -defmodule Integration.Commands.RelayTest do +defmodule Cog.Test.Commands.RelayTest do use Cog.AdapterCase, adapter: "test" alias Cog.Support.ModelUtilities alias Cog.Models.Relay alias Cog.Repo + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/commands/role_test.exs b/test/commands/role_test.exs similarity index 99% rename from test/integration/commands/role_test.exs rename to test/commands/role_test.exs index 6dd55282..cde032a4 100644 --- a/test/integration/commands/role_test.exs +++ b/test/commands/role_test.exs @@ -1,4 +1,4 @@ -defmodule Integration.Commands.RoleTest do +defmodule Cog.Test.Commands.RoleTest do use Cog.AdapterCase, adapter: "test" alias Cog.Repository.Roles @@ -6,6 +6,8 @@ defmodule Integration.Commands.RoleTest do alias Cog.Models.Role + @moduletag :skip + import DatabaseAssertions, only: [assert_role_is_granted: 2, refute_role_is_granted: 2] diff --git a/test/integration/commands/rule_test.exs b/test/commands/rule_test.exs similarity index 99% rename from test/integration/commands/rule_test.exs rename to test/commands/rule_test.exs index 42331d6c..89953d26 100644 --- a/test/integration/commands/rule_test.exs +++ b/test/commands/rule_test.exs @@ -1,4 +1,4 @@ -defmodule Integration.Commands.RuleTest do +defmodule Cog.Test.Commands.RuleTest do use Cog.AdapterCase, adapter: "test" alias Cog.Models.Rule @@ -6,6 +6,8 @@ defmodule Integration.Commands.RuleTest do require Ecto.Query + @moduletag :skip + @bad_uuid "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" setup do diff --git a/test/integration/commands/seed_test.exs b/test/commands/seed_test.exs similarity index 86% rename from test/integration/commands/seed_test.exs rename to test/commands/seed_test.exs index 776f960d..2a4218ea 100644 --- a/test/integration/commands/seed_test.exs +++ b/test/commands/seed_test.exs @@ -1,6 +1,7 @@ -defmodule Integration.Commands.SeedTest do +defmodule Cog.Test.Commands.SeedTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip setup do user = user("jfrost", first_name: "Jim", last_name: "Frost") diff --git a/test/integration/commands/sleep_test.exs b/test/commands/sleep_test.exs similarity index 85% rename from test/integration/commands/sleep_test.exs rename to test/commands/sleep_test.exs index 1822c67e..070f6725 100644 --- a/test/integration/commands/sleep_test.exs +++ b/test/commands/sleep_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.SleepTest do +defmodule Cog.Test.Commands.SleepTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("jfrost", first_name: "Jim", last_name: "Frost") |> with_chat_handle_for("test") diff --git a/test/integration/commands/sort_test.exs b/test/commands/sort_test.exs similarity index 93% rename from test/integration/commands/sort_test.exs rename to test/commands/sort_test.exs index 73487650..42f39aa2 100644 --- a/test/integration/commands/sort_test.exs +++ b/test/commands/sort_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.SortTest do +defmodule Cog.Test.Commands.SortTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("jfrost", first_name: "Jim", last_name: "Frost") |> with_chat_handle_for("test") diff --git a/test/integration/commands/trigger_test.exs b/test/commands/trigger_test.exs similarity index 98% rename from test/integration/commands/trigger_test.exs rename to test/commands/trigger_test.exs index 450e66e1..fd9c577f 100644 --- a/test/integration/commands/trigger_test.exs +++ b/test/commands/trigger_test.exs @@ -1,7 +1,9 @@ -defmodule Integration.Commands.TriggerTest do +defmodule Cog.Test.Commands.TriggerTest do use Cog.AdapterCase, adapter: "test" require Logger + @moduletag :skip + alias Cog.Models.Trigger alias Cog.Repository.Triggers diff --git a/test/integration/commands/unique_test.exs b/test/commands/unique_test.exs similarity index 85% rename from test/integration/commands/unique_test.exs rename to test/commands/unique_test.exs index f4fa417f..f7782608 100644 --- a/test/integration/commands/unique_test.exs +++ b/test/commands/unique_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.UniqueTest do +defmodule Cog.Test.Commands.UniqueTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("jfrost", first_name: "Jim", last_name: "Frost") |> with_chat_handle_for("test") diff --git a/test/integration/commands/user_test.exs b/test/commands/user_test.exs similarity index 98% rename from test/integration/commands/user_test.exs rename to test/commands/user_test.exs index 802d4a04..dfaf04ba 100644 --- a/test/integration/commands/user_test.exs +++ b/test/commands/user_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.UserTest do +defmodule Cog.Test.Commands.UserTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("tester") |> with_chat_handle_for("test") diff --git a/test/integration/commands/which_test.exs b/test/commands/which_test.exs similarity index 96% rename from test/integration/commands/which_test.exs rename to test/commands/which_test.exs index 0e90b7b5..051be786 100644 --- a/test/integration/commands/which_test.exs +++ b/test/commands/which_test.exs @@ -1,6 +1,8 @@ -defmodule Integration.Commands.WhichTest do +defmodule Cog.Test.Commands.WhichTest do use Cog.AdapterCase, adapter: "test" + @moduletag :skip + setup do user = user("vanstee", first_name: "Patrick", last_name: "Van Stee") |> with_chat_handle_for("test") diff --git a/test/integration/hipchat_test.exs b/test/integration/hipchat_test.exs new file mode 100644 index 00000000..388e199f --- /dev/null +++ b/test/integration/hipchat_test.exs @@ -0,0 +1,118 @@ +defmodule Integration.HipChatTest do + use Cog.Test.Support.ProviderCase, provider: :hipchat + + @user "botci" + + @bot "deckard" + @bot_name "Rich Deckard" + + @ci_room "ci_bot_testing" + @timeout 30000 + + setup do + # The user always interacts with the bot via the `@user` account + # (see above). Our helper functions set up a user with the same + # Cog username and Slack handle + user = user(@user) + |> with_chat_handle_for("hipchat") + {:ok, client} = ChatClient.new() + {:ok, %{user: user, client: client}} + end + + test "running the st-echo command", %{user: user, client: client} do + user |> with_permission("operable:st-echo") + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: "@#{@bot} operable:st-echo test", reply_from: @bot_name, timeout: @timeout]) + assert reply.text == "test" + end + + test "running the st-echo command without permission", %{client: client} do + message = "@#{@bot}: operable:st-echo test" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, + reply_from: @bot_name, timeout: @timeout]) + expected = """ + Pipeline: operable:st-echo test
    Caller: @botci

    The pipeline failed executing the command:

    operable:st-echo test
    +    


    The specific error was:

    Sorry, you aren't allowed to execute 'operable:st-echo test' :(
    +     You will need at least one of the following permissions to run this command: 'operable:st-echo'.
    +    
    + """ |> String.strip + assert String.contains?(reply.text, expected) + assert reply.location.type == :channel + assert reply.location.name == @ci_room + end + + test "running commands in a pipeline", %{user: user, client: client} do + user + |> with_permission("operable:echo") + |> with_permission("operable:thorn") + + message = "@#{@bot}: seed '[{\"test\": \"blah\"}]' | echo $test" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.text == "blah" + assert reply.location.type == :channel + assert reply.location.name == @ci_room + end + + test "running commands in a pipeline without permission", %{user: user, client: client} do + user |> with_permission("operable:st-echo") + + message = "@#{@bot}: operable:st-echo \"this is a test\" | operable:st-thorn $body" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + expected = """ + Pipeline: operable:st-echo "this is a test" | operable:st-thorn $body
    Caller: @botci

    The pipeline failed executing the command:

    operable:st-thorn $body
    +    


    The specific error was:

    Sorry, you aren't allowed to execute 'operable:st-thorn $body' :(
    +     You will need at least one of the following permissions to run this command: 'operable:st-thorn'.
    +    
    + """ |> String.strip + assert String.contains?(reply.text, expected) + assert reply.location.type == :channel + assert reply.location.name == @ci_room + end + + test "redirecting to a private channel", %{user: user, client: client} do + user |> with_permission("operable:echo") + time = "#{System.os_time()}" + private_channel = "private_ci_testing" + message = "@#{@bot}: operable:echo #{time} > #{private_channel}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.location.type == :channel + assert reply.location.name == private_channel + assert reply.text == time + end + + test "redirecting to 'here'", %{user: user, client: client} do + user |> with_permission("operable:echo") + message = "@#{@bot}: operable:echo blah > here" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.text == "blah" + assert reply.location.type == :channel + assert reply.location.name == @ci_room + end + + test "redirecting to 'me'", %{user: user, client: client} do + user |> with_permission("operable:echo") + + message = "@#{@bot}: operable:echo blah > me" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.location.type == :im + assert reply.text == "blah" + end + + test "redirecting to a specific user", %{user: user, client: client} do + user |> with_permission("operable:echo") + message = "@#{@bot}: operable:echo blah > #{@user}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.text == "blah" + assert reply.location.type == :im + end + + test "redirecting to another channel", %{user: user, client: client} do + user |> with_permission("operable:echo") + redirect_channel = "ci_bot_redirect_tests" + message = "@#{@bot}: operable:echo blah > #{redirect_channel}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot_name, timeout: @timeout]) + assert reply.location.type == :channel + assert reply.location.name == redirect_channel + assert reply.text == "blah" + end + +end diff --git a/test/integration/relay_test.exs b/test/integration/relay_test.exs deleted file mode 100644 index 1afe9ed8..00000000 --- a/test/integration/relay_test.exs +++ /dev/null @@ -1,119 +0,0 @@ -defmodule Integration.RelayTest do - use Cog.AdapterCase, adapter: "test" - alias Carrier.Messaging - - @moduletag :relay - - @relays_discovery_topic "bot/relays/discover" - @timeout 60000 # 60 seconds - - setup do - user = user("botci") - |> with_chat_handle_for("test") - - conn = subscribe_to_relay_discover - - checkout_relay - build_relay - os_pid = start_relay - wait_for_relay(conn) - - checkout_mist - build_mist - install_mist - wait_for_mist(conn) - - on_exit(fn -> - stop_relay(os_pid) - disconnect_from_relay_discover(conn) - end) - - {:ok, %{user: user}} - end - - test "running command from newly installed bundle", %{user: user} do - response = send_message(user, "@bot: help mist:ec2-find") |> Map.get("response") - assert response == """ - { - "documentation": "mist:ec2-find --region= [--state | --tags | --ami | --return=(id,pubdns,privdns,state,keyname,ami,kernel,arch,vpc,pubip,privip,az,tags)]", - "command": "mist:ec2-find" - } - """ |> String.rstrip - end - - # Runs `rm -rf` on the path before checking out the repo - defp checkout(name) do - Mix.SCM.Git.checkout(dest: "../cog_#{name}", git: "git@github.com:operable/#{name}.git") - end - - defp checkout_relay do - checkout("relay") - end - - defp checkout_mist do - checkout("mist") - end - - defp build_relay do - System.cmd("mix", ["deps.get"], cd: "../cog_relay") - end - - defp start_relay do - port = Port.open({:spawn, "iex -S mix"}, cd: "../cog_relay", env: [{'COG_MQTT_PORT', '1884'}]) - {:os_pid, os_pid} = Port.info(port, :os_pid) - os_pid - end - - defp stop_relay(os_pid) do - System.cmd("kill", ["-9", to_string(os_pid)]) - end - - defp build_mist do - System.cmd("make", ["install"], cd: "../cog_mist") - System.cmd("make", [], cd: "../cog_mist") - end - - defp install_mist do - System.cmd("cp", ["mist.cog", "../cog_relay/data/pending"], cd: "../cog_mist") - end - - defp subscribe_to_relay_discover do - {:ok, conn} = Messaging.Connection.connect - Messaging.Connection.subscribe(conn, @relays_discovery_topic) - conn - end - - defp wait_for_relay(conn) do - receive do - {:publish, @relays_discovery_topic, message} -> - message = Poison.decode!(message) - - case match?(%{"intro" => _relay}, message) do - true -> true - false -> wait_for_relay(conn) - end - after @timeout -> - disconnect_from_relay_discover(conn) - raise(RuntimeError, "Connection timeout out waiting for relay to start") - end - end - - defp wait_for_mist(conn) do - receive do - {:publish, @relays_discovery_topic, message} -> - message = Poison.decode!(message) - - case match?(%{"announce" => %{"bundles" => [%{"bundle" => %{"name" => "mist"}}]}}, message) do - true -> true - false -> wait_for_mist(conn) - end - after @timeout -> - disconnect_from_relay_discover(conn) - raise(RuntimeError, "Connection timeout out waiting for mist to be installed") - end - end - - defp disconnect_from_relay_discover(conn) do - :emqttc.disconnect(conn) - end -end diff --git a/test/integration/slack_registration_test.exs b/test/integration/slack_registration_test.exs index 8a8449f0..5b3d2efe 100644 --- a/test/integration/slack_registration_test.exs +++ b/test/integration/slack_registration_test.exs @@ -5,6 +5,7 @@ defmodule Integration.SlackRegistrationTest do alias Cog.Models.User @moduletag :slack + @moduletag :integration # Name of the Slack user we'll be interacting with the bot as @user "botci" diff --git a/test/integration/slack_test.exs b/test/integration/slack_test.exs index 104e1ac8..8eb4bc8d 100644 --- a/test/integration/slack_test.exs +++ b/test/integration/slack_test.exs @@ -1,111 +1,156 @@ defmodule Integration.SlackTest do - use Cog.AdapterCase, adapter: "slack" + use Cog.Test.Support.ProviderCase, provider: :slack - @moduletag :slack - - # Name of the Slack user we'll be interacting with the bot as @user "botci" - # Name of the bot we'll be operating as @bot "deckard" + @ci_room "ci_bot_testing" + setup do + # Wait random 1-3 second interval to avoid being throttled by Slack's API + interval = :random.uniform(3) * 1000 + :timer.sleep(interval) + # The user always interacts with the bot via the `@user` account # (see above). Our helper functions set up a user with the same # Cog username and Slack handle user = user(@user) |> with_chat_handle_for("slack") - {:ok, %{user: user}} + {:ok, client} = ChatClient.new() + {:ok, %{user: user, client: client}} end - test "running the st-echo command", %{user: user} do + test "running the st-echo command", %{user: user, client: client} do user |> with_permission("operable:st-echo") - - message = send_message("@#{@bot}: operable:st-echo test") - assert_response "test", after: message + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: "@#{@bot} operable:st-echo test", reply_from: @bot]) + assert reply.text == "test" end - test "running the st-echo command without permission" do - message = send_message("@#{@bot}: operable:st-echo test") - assert_response_contains "Sorry, you aren't allowed to execute 'operable:st-echo test' :(\n You will need at least one of the following permissions to run this command: 'operable:st-echo'", after: message + test "running the st-echo command without permission", %{client: client} do + message = "@#{@bot}: operable:st-echo test" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, + reply_from: @bot]) + expected = """ + The pipeline failed executing the command: + + ```operable:st-echo test + ``` + + The specific error was: + + ```Sorry, you aren't allowed to execute 'operable:st-echo test' :( + You will need at least one of the following permissions to run this command: 'operable:st-echo'. + ``` + """ |> String.strip + assert reply.text == expected + assert reply.location.type == :channel + assert reply.location.name == @ci_room end - test "running commands in a pipeline", %{user: user} do + test "running commands in a pipeline", %{user: user, client: client} do user |> with_permission("operable:echo") |> with_permission("operable:thorn") - message = send_message(~s(@#{@bot}: seed '[{"test": "blah"}]' | echo $test)) - assert_response "blah", after: message + message = "@#{@bot}: seed '[{\"test\": \"blah\"}]' | echo $test" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.text == "blah" + assert reply.location.type == :channel + assert reply.location.name == @ci_room end - test "running commands in a pipeline without permission", %{user: user} do + test "running commands in a pipeline without permission", %{user: user, client: client} do user |> with_permission("operable:st-echo") - message = send_message(~s(@#{@bot}: operable:st-echo "this is a test" | operable:st-thorn $body)) - assert_response_contains "Sorry, you aren't allowed to execute 'operable:st-thorn $body' :(\n You will need at least one of the following permissions to run this command: 'operable:st-thorn'", after: message - end + message = "@#{@bot}: operable:st-echo \"this is a test\" | operable:st-thorn $body" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + expected = """ + The pipeline failed executing the command: - test "sending a message to a group", %{user: user} do - user |> with_permission("operable:echo") - private_channel = "#group_ci_bot_testing" + ```operable:st-thorn $body + ``` - message = send_message("@#{@bot}: operable:echo blah", private_channel) - assert_response "blah", [after: message], private_channel + The specific error was: + + ```Sorry, you aren't allowed to execute 'operable:st-thorn $body' :( + You will need at least one of the following permissions to run this command: 'operable:st-thorn'. + ``` + """ |> String.strip + assert reply.text == expected + assert reply.location.type == :channel + assert reply.location.name == @ci_room end - test "redirecting from a private channel", %{user: user} do + test "sending a message to a group", %{user: user, client: client} do user |> with_permission("operable:echo") - private_channel = "#group_ci_bot_testing" - marker = send_message("Redirect test starts here") + private_group = "group_ci_bot_testing" - send_message("@#{@bot}: operable:echo blah > #ci_bot_testing", private_channel) - assert_response "blah", [after: marker] + message = "@#{@bot}: operable:echo blah" + {:ok, reply} = ChatClient.chat_wait!(client, [room: private_group, message: message, reply_from: @bot]) + assert reply.text == "blah" + assert reply.location.type == :group + assert reply.location.name == private_group end - test "redirecting to a private channel", %{user: user} do + test "redirecting from a private channel", %{user: user, client: client} do user |> with_permission("operable:echo") - private_channel = "#group_ci_bot_testing" - marker = send_message("Redirect test starts here", private_channel) - - send_message("@#{@bot}: operable:echo blah > #{private_channel}") - assert_response "blah", [after: marker], private_channel + private_channel = "group_ci_bot_testing" + message = "@#{@bot}: operable:echo blah > ##{private_channel}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.location.type == :group + assert reply.location.name == private_channel + assert reply.text == "blah" end - test "redirecting to 'here'", %{user: user} do + test "redirecting to a private channel", %{user: user, client: client} do user |> with_permission("operable:echo") - - message = send_message("@#{@bot}: operable:echo blah > here") - assert_response "blah", [after: message] + time = "#{System.os_time()}" + private_channel = "group_ci_bot_testing" + message = "@#{@bot}: operable:echo #{time} > ##{private_channel}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.location.type == :group + assert reply.location.name == private_channel + assert reply.text == time end - test "redirecting to 'me'", %{user: user} do + test "redirecting to 'here'", %{user: user, client: client} do user |> with_permission("operable:echo") - marker = send_message("echo here", "@#{@user}") - - send_message("@#{@bot}: operable:echo blah > me") - # Since Cog responds when direct messaging it we have to assert - # that both our marker text and message test exist. - assert_response "here\nblah", [after: marker, count: 2], "@#{@user}" + message = "@#{@bot}: operable:echo blah > here" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.text == "blah" + assert reply.location.type == :channel + assert reply.location.name == @ci_room end - test "redirecting to a specific user", %{user: user} do + test "redirecting to 'me'", %{user: user, client: client} do user |> with_permission("operable:echo") - marker = send_message("echo here", "@#{@user}") - send_message("@#{@bot}: operable:echo blah > @#{@user}") + message = "@#{@bot}: operable:echo blah > me" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.location.type == :im # Since Cog responds when direct messaging it we have to assert # that both our marker text and message test exist. - assert_response "here\nblah", [after: marker, count: 2], "@#{@user}" + #assert_response "here\nblah", [after: marker, count: 2], "@#{@user}" end - test "redirecting to another channel", %{user: user} do + test "redirecting to a specific user", %{user: user, client: client} do user |> with_permission("operable:echo") - redirect_channel = "#ci_bot_redirect_tests" - marker = send_message("echo here", redirect_channel) + message = "@#{@bot}: operable:echo blah > @#{@user}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.text == "blah" + assert reply.location.type == :im + end - send_message("@#{@bot}: operable:echo blah > #{redirect_channel}") - assert_response "blah", [after: marker], redirect_channel + test "redirecting to another channel", %{user: user, client: client} do + user |> with_permission("operable:echo") + redirect_channel = "ci_bot_redirect_tests" + message = "@#{@bot}: operable:echo blah > ##{redirect_channel}" + {:ok, reply} = ChatClient.chat_wait!(client, [room: @ci_room, message: message, reply_from: @bot]) + assert reply.location.type == :channel + assert reply.location.name == redirect_channel + assert reply.text == "blah" end + end diff --git a/test/support/adapter_case.ex b/test/support/adapter_case.ex index 2f05058c..631b1d7c 100644 --- a/test/support/adapter_case.ex +++ b/test/support/adapter_case.ex @@ -1,5 +1,4 @@ defmodule Cog.AdapterCase do - alias ExUnit.CaptureLog alias Cog.Repo alias Cog.Bootstrap @@ -29,10 +28,10 @@ defmodule Cog.AdapterCase do setup_all do case maybe_replace_chat_provider(unquote(adapter)) do {:ok, original_provider} -> - restart_application + reload_chat_adapter() on_exit(fn -> maybe_replace_chat_provider(original_provider) - restart_application + reload_chat_adapter() end) :no_change -> :ok @@ -97,11 +96,14 @@ defmodule Cog.AdapterCase do defp provider_for(other), do: raise "I don't know what implements the #{other} provider yet!" - def restart_application do - CaptureLog.capture_log(fn -> - :ok = Application.stop(:cog) - :ok = Application.start(:cog) - end) + def reload_chat_adapter() do + case :erlang.whereis(Cog.Chat.Adapter) do + :undefined -> + Logger.error("Can't find Cog.Chat.Adapter process!") + :erlang.halt(4) + pid -> + :erlang.exit(pid, :kill) + end end def bootstrap do diff --git a/test/support/hipchat_client.ex b/test/support/hipchat_client.ex new file mode 100644 index 00000000..069a1b24 --- /dev/null +++ b/test/support/hipchat_client.ex @@ -0,0 +1,135 @@ +defmodule Cog.Test.Support.HipChatClient do + + @default_timeout 5000 + @api_root "https://api.hipchat.com/v2" + @conf_host "conf.hipchat.com" + + use GenServer + + alias Romeo.Connection + alias Romeo.Stanza + alias Cog.Chat.HipChat.Util + alias Cog.Chat.HipChat.Rooms + alias Cog.Chat.HipChat.Users + + defstruct [:hipchat_org, :mention_name, :conn, :rooms, :users, :waiters] + + # Required for uniform interface between Slack and HipChat + # test clients + def new(), do: __MODULE__.start_link() + + def start_link() do + api_token = get_env!("HIPCHAT_USER_API_TOKEN") + xmpp_jid = get_env!("HIPCHAT_USER_JID") + xmpp_password = get_env!("HIPCHAT_USER_PASSWORD") + nickname = get_env!("HIPCHAT_USER_NICKNAME") + GenServer.start_link(__MODULE__, [api_token, xmpp_jid, xmpp_password, nickname]) + end + + def init([api_token, xmpp_jid, xmpp_password, nickname]) do + opts = [host: "chat.hipchat.com", + jid: xmpp_jid, + password: xmpp_password, + require_tls: true] + [user_name|_] = String.split(xmpp_jid, "@", parts: 2) + [hipchat_org|_] = String.split(user_name, "_", parts: 2) + case Connection.start_link(opts) do + {:ok, conn} -> + Connection.send(conn, Stanza.presence) + Connection.send(conn, Stanza.join("#{hipchat_org}_ci_bot_testing@conf.hipchat.com", nickname)) + Connection.send(conn, Stanza.join("#{hipchat_org}_ci_bot_redirect_tests@conf.hipchat.com", nickname)) + Connection.send(conn, Stanza.join("#{hipchat_org}_private_ci_testing@conf.hipchat.com", nickname)) + {:ok, %__MODULE__{conn: conn, waiters: %{}, rooms: Rooms.new(@api_root, api_token), + users: %Users{}, hipchat_org: hipchat_org, + mention_name: nickname}} + error -> + error + end + end + + def chat_wait!(client, opts) do + room = Keyword.fetch!(opts, :room) + message = Keyword.fetch!(opts, :message) + reply_from = Keyword.fetch!(opts, :reply_from) + timeout = Keyword.get(opts, :timeout, @default_timeout) + GenServer.call(client, {:chat_and_wait, room, message, reply_from}, timeout) + end + + def handle_call({:chat_and_wait, room, message, reply_from}, from, state) do + {room, rooms} = Rooms.lookup(state.rooms, state.conn, name: room) + state = send_room_message(room, message, state) + {:noreply, %{state | rooms: rooms, waiters: Map.update(state.waiters, reply_from, [from], + fn(waiters) -> [from|waiters] end)}} + end + + def handle_info({:stanza, %Stanza.Message{}=message}, state) do + state = case Util.classify_message(message) do + {:invite, room_name} -> + join_room(room_name, state) + state + {:groupchat, room_jid, sender, body} -> + handle_groupchat(room_jid, sender, body, state) + {:dm, sender_jid, body} -> + {user, users} = Users.lookup(state.users, state.conn, jid: sender_jid) + state = %{state | users: users} + handle_dm(user.mention_name, body, state) + :ignore -> + state + end + {:noreply, state} + end + def handle_info(_ignored, state) do + {:noreply, state} + end + + defp handle_dm(sender, body, state) do + case Map.get(state.waiters, sender) do + nil -> + state + waiters -> + location = %{type: :im} + message = %{location: location, text: body} + Enum.each(waiters, fn(waiter) -> GenServer.reply(waiter, {:ok, message}) end) + %{state | waiters: Map.delete(state.waiters, sender)} + end + end + defp handle_groupchat(room_jid, sender, body, state) do + {room, rooms} = Rooms.lookup(state.rooms, state.conn, jid: room_jid) + state = %{state | rooms: rooms} + case Map.get(state.waiters, sender) do + nil -> + state + waiters -> + location = %{type: :channel, name: room.name} + message = %{text: body, location: location} + Enum.each(waiters, fn(waiter) -> GenServer.reply(waiter, {:ok, message}) end) + %{state | waiters: Map.delete(state.waiters, sender)} + end + end + + defp get_env!(name) do + case System.get_env(name) do + nil -> + raise RuntimeError, message: "$#{name} not set!" + value -> + value + end + end + + defp send_room_message(room, message, state) do + Connection.send(state.conn, Stanza.groupchat(room.id, message)) + state + end + + defp join_room(room_name, state) do + # If we were given a JID then use it, else build a JID + # using the hipchat org and conf_host + muc_name = if String.contains?(room_name, @conf_host) do + room_name + else + "#{state.hipchat_org}_#{room_name}@#{@conf_host}" + end + Connection.send(state.conn, Stanza.join(muc_name, state.mention_name)) + end + +end diff --git a/test/support/provider_case.ex b/test/support/provider_case.ex new file mode 100644 index 00000000..6487e675 --- /dev/null +++ b/test/support/provider_case.ex @@ -0,0 +1,151 @@ +defmodule Cog.Test.Support.ProviderCase do + alias Cog.Repo + alias Cog.Bootstrap + + require Logger + + + + @vcr_adapter ExVCR.Adapter.IBrowse + + defmacro __using__([provider: provider]) do + case client_for_provider(provider) do + nil -> + raise RuntimeError, message: "Unknown chat provider #{provider}" + client_mod -> + quote do + + @moduletag [unquote(provider), :integration] + + require Logger + use ExUnit.Case, async: false + + import unquote(__MODULE__) + import Cog.Support.ModelUtilities + import ExUnit.Assertions + + alias unquote(client_mod), as: ChatClient + + # Only restart the application if we're actually changing the + # chat provider to something that it's not already configured + # for. + setup_all do + case maybe_replace_chat_provider(unquote(provider)) do + {:ok, original_provider} -> + reload_chat_adapter() + on_exit(fn -> + maybe_replace_chat_provider(original_provider) + reload_chat_adapter() + end) + :no_change -> + :ok + end + :ok + end + + setup context do + # recorder = start_recorder(unquote(provider), context) + + Ecto.Adapters.SQL.Sandbox.checkout(Repo) + Ecto.Adapters.SQL.Sandbox.mode(Repo, {:shared, self()}) + + bootstrap + Cog.Command.PermissionsCache.reset_cache + + # on_exit(fn -> + # stop_recorder(recorder) + # end) + + :ok + end + end + end + end + + # If the currently-defined chat provider is different from the one + # we want to set, then we'll switch out the existing one for the new + # one, remembering what the original was so we can reset it at the + # end of the test. + # + # If we're already running on the requested chat provider, then + # we'll make no change to the application configuration and return + # `:no_change`, indicating that we don't need to restart the + # application. + def maybe_replace_chat_provider(string) when is_binary(string), + do: maybe_replace_chat_provider(String.to_existing_atom(string)) + def maybe_replace_chat_provider(new_provider) do + config = Application.get_env(:cog, Cog.Chat.Adapter) + old_provider = Keyword.fetch!(config, :chat) + + if old_provider == new_provider do + :no_change + else + providers = config + |> Keyword.fetch!(:providers) + |> Keyword.delete(old_provider) + |> Keyword.put(new_provider, provider_for(new_provider)) + + config = config + |> Keyword.put(:providers, providers) + |> Keyword.put(:chat, new_provider) + + Application.put_env(:cog, Cog.Chat.Adapter, config) + + {:ok, old_provider} + end + end + + defp provider_for(:slack), do: Cog.Chat.Slack.Provider + defp provider_for(:hipchat), do: Cog.Chat.HipChat.Provider + defp provider_for(:test), do: Cog.Chat.Test.Provider + defp provider_for(other), + do: raise "I don't know what implements the #{other} provider yet!" + + def reload_chat_adapter() do + case :erlang.whereis(Cog.Chat.Adapter) do + :undefined -> + Logger.error("Can't find Cog.Chat.Adapter process!") + :erlang.halt(4) + pid -> + :erlang.exit(pid, :kill) + end + end + + def bootstrap do + without_logger(fn -> + Bootstrap.bootstrap + end) + end + + def without_logger(fun) do + Logger.disable(self) + fun.() + Logger.enable(self) + end + + # The following recorder functions were adapted from ExVCR's `use_cassette` + # function which could not be easily used here. + def start_recorder("test", _context), do: nil + def start_recorder(_adapter, context) do + fixture = ExVCR.Mock.normalize_fixture("#{context.case}.#{context.test}") + recorder = ExVCR.Recorder.start(fixture: fixture, adapter: @vcr_adapter, match_requests_on: [:query, :request_body]) + + ExVCR.Mock.mock_methods(recorder, @vcr_adapter) + + recorder + end + + def stop_recorder(nil), do: nil + def stop_recorder(recorder) do + try do + :meck.unload(@vcr_adapter.module_name) + after + ExVCR.Recorder.save(recorder) + end + end + + defp client_for_provider(:slack), do: Cog.Test.Support.SlackClient + defp client_for_provider(:hipchat), do: Cog.Test.Support.HipChatClient + defp client_for_provider(_), do: nil + +end diff --git a/test/support/slack_client.ex b/test/support/slack_client.ex new file mode 100644 index 00000000..b361d88c --- /dev/null +++ b/test/support/slack_client.ex @@ -0,0 +1,273 @@ +defmodule Cog.Test.Support.SlackClientState do + + def start_link() do + Agent.start_link(fn -> fresh_state end, name: __MODULE__) + end + + def get_users(token) do + case Agent.get(__MODULE__, fn(state) -> Map.get(state, :users) end) do + nil -> + :timer.sleep(500) + result = Slack.Web.Users.list(%{token: token}) + if Map.get(result, "ok") == true do + Agent.update(__MODULE__, fn(state) -> Map.put(state, :users, result["members"]) end) + result["members"] + else + {:error, result["error"]} + end + users -> + users + end + end + + def store_message(sender, message) do + Agent.update(__MODULE__, + fn(state) -> + messages = Map.get(state, :messages) + messages = Map.update(messages, sender, [message], + fn(previous) -> + Enum.sort([message|previous], &by_ts/2) + end) + Map.put(state, :messages, messages) + end) + end + + def get_messages(sender) do + Agent.get(__MODULE__, fn(state) -> + state + |> Map.get(:messages) + |> Map.get(sender) + end) + end + + def add_waiter(reply_from, caller) do + add_callback(:waiters, reply_from, caller) + end + + def get_waiters(reply_from) do + get_callbacks(:waiters, reply_from) + end + + def reset() do + Agent.update(__MODULE__, fn(_) -> fresh_state end) + end + + def fresh_state() do + %{} |> Map.put(:messages, %{}) |> Map.put(:waiters, %{}) + end + + defp add_callback(key, reply_from, caller) do + Agent.update(__MODULE__, fn(state) -> + things = Map.fetch!(state, key) + things = Map.update(things, reply_from, [caller], + &([caller|&1])) + Map.put(state, key, things) end) + end + + defp get_callbacks(key, reply_from) do + Agent.get_and_update(__MODULE__, fn(state) -> + things = Map.fetch!(state, key) + case Map.get(things, reply_from) do + nil -> + {nil, state} + callers -> + things = Map.delete(things, reply_from) + state = Map.put(state, key, things) + {callers, state} + end + end) + end + + defp by_ts(first, second) do + first.ts > second.ts + end + +end + +defmodule Cog.Test.Support.SlackClient do + + # To avoid Slack throttling + @api_wait_interval 750 + + @default_timeout 5000 + + use Slack + + alias Cog.Test.Support.SlackClientState + + def new() do + case System.get_env("SLACK_USER_API_TOKEN") do + nil -> + raise RuntimeError, message: "$SLACK_USER_API_TOKEN not set!" + token -> + {:ok, client} = start_link(token) + :ok = initialize(client) + {:ok, client} + end + end + + def initialize(client, room \\ nil) do + call(client, {:init, room}) + end + + def reset(client) do + call(client, :reset) + end + + def chat_wait!(client, opts) do + room = Keyword.fetch!(opts, :room) + message = Keyword.fetch!(opts, :message) + reply_from = Keyword.fetch!(opts, :reply_from) + edited = Keyword.get(opts, :edited) + timeout = Keyword.get(opts, :timeout, @default_timeout) + call(client, {:chat_and_wait, room, message, reply_from, edited}, timeout) + end + + def get_messages(client, sender) do + call(client, {:get_messages, sender}) + end + + def get_messages(client, sender, count) do + case get_messages(client, sender) do + {:ok, messages} -> + Enum.take(messages, count) + error -> + error + end + end + + def handle_message(%{subtype: "message_changed"}, _state) do + :ok + end + def handle_message(%{type: "message"}=message, state) do + if message.user != state.me.id do + msg = parse_message(message, state) + SlackClientState.store_message(message.user, msg) + case SlackClientState.get_waiters(message.user) do + nil -> + :ok + callers -> + reply = {:ok, msg} + Enum.each(callers, fn({ref, caller}) -> send(caller, {ref, reply}) end) + end + end + :ok + end + def handle_message(_message, _state) do + :ok + end + + def handle_info({{ref, sender}, {:init, room}}, state) do + SlackClientState.start_link() + reply = if room != nil do + result = Slack.Web.Channels.join(room, %{token: state.token, as_user: true}) + if result["ok"] == true do + :ok + else + {:error, result["error"]} + end + else + :ok + end + send(sender, {ref, reply}) + {:noreply, state} + end + def handle_info({{ref, sender}=caller, {:chat_and_wait, room, message, reply_from, nil}}, state) do + :timer.sleep(@api_wait_interval) + result = Slack.Web.Chat.post_message(room, %{as_user: true, text: message, token: state.token, parse: :full}) + case result["ok"] do + false -> + send(sender, {ref, {:error, result["error"]}}) + {:noreply, state} + true -> + user_id = find_user!(reply_from, state) + SlackClientState.add_waiter(user_id, caller) + {:noreply, state} + end + end + def handle_info({{ref, sender}=caller, {:chat_and_wait, room, message, reply_from, edited}}, state) do + :timer.sleep(@api_wait_interval) + result = Slack.Web.Chat.post_message(room, %{as_user: true, text: message, token: state.token}) + case result["ok"] do + false -> + send(sender, {ref, {:error, result["error"]}}) + {:noreply, state} + true -> + result = Slack.Web.Chat.update(result["channel"], edited, result["ts"], %{token: state.token, as_user: true, parse: :full}) + case result["ok"] do + false -> + send(sender, {ref, {:error, result["error"]}}) + {:noreply, state} + true -> + user_id = find_user!(reply_from, state) + SlackClientState.add_waiter(user_id, caller) + {:noreply, state} + end + end + end + def handle_info({{ref, sender}, {:get_messages, chat_user}}, state) do + user_id = find_user!(chat_user, state) + messages = SlackClientState.get_messages(user_id) + send(sender, {ref, {:ok, messages}}) + {:noreply, state} + end + def handle_info({{ref, sender}, :reset}, state) do + SlackClientState.reset() + send(sender, {ref, :ok}) + {:noreply, state} + end + + defp call(sender, data, timeout \\ @default_timeout) do + ref = :erlang.make_ref() + message = {{ref, self()}, data} + send(sender, message) + receive do + {^ref, result} -> + result + after timeout -> + {:error, :timeout} + end + end + + defp find_user!(name, state) do + case SlackClientState.get_users(state.token) do + {:error, reason} -> + raise RuntimeError, message: "Failed fetching Slack users: #{inspect reason}" + users -> + Enum.find(users, &(Map.get(&1, "name") == name)) |> Map.get("id") + end + end + + defp parse_message(%{attachments: [attachment|_], ts: ts}=message, state) do + make_message(attachment.text, ts, location(message, state)) + end + defp parse_message(%{text: text, ts: ts}=message, state) do + make_message(text, ts, location(message, state)) + end + + defp make_message(text, ts, locn) do + [realtime, _] = String.split(ts, ".", parts: 2) + %{ts: ts, real_time: String.to_integer(realtime), text: text, location: locn} + end + + defp location(%{channel: %{is_im: true}}, _state) do + %{type: :im} + end + defp location(%{channel: id}, state) do + case id do + <<"D", _::binary>> -> + %{type: :im} + <<"G", _::binary>> -> + group = Slack.Web.Groups.info(id, %{token: state.token}) + group = group["group"] + %{type: :group, + name: Map.get(group, "name")} + _ -> + channel = Slack.Web.Channels.info(id, %{token: state.token}) + channel = channel["channel"] + %{type: :channel, + name: Map.get(channel, "name")} + end + end + +end diff --git a/test/support/template_case.ex b/test/support/template_case.ex index 88b79081..ef0ba188 100644 --- a/test/support/template_case.ex +++ b/test/support/template_case.ex @@ -2,6 +2,8 @@ defmodule Cog.TemplateCase do use ExUnit.CaseTemplate, async: true alias Cog.Template.New + alias Cog.Chat.Slack.TemplateProcessor, as: SlackProcessor + alias Cog.Chat.HipChat.TemplateProcessor, as: HipChatProcessor using do quote do @@ -10,14 +12,20 @@ defmodule Cog.TemplateCase do end end + def assert_rendered_template(:hipchat, bundle, template_name, data, expected) when is_binary(expected) do + directives = directives_for_template(bundle, template_name, data) + rendered = HipChatProcessor.render(directives) + assert expected == rendered + end + def assert_rendered_template(:slack, bundle, template_name, data, expected) when is_binary(expected) do directives = directives_for_template(bundle, template_name, data) - {rendered, _} = Cog.Chat.Slack.TemplateProcessor.render(directives) + {rendered, _} = SlackProcessor.render(directives) assert expected == rendered end def assert_rendered_template(:slack, bundle, template_name, data, {text, attachments}) do directives = directives_for_template(bundle, template_name, data) - {message, rendered} = Cog.Chat.Slack.TemplateProcessor.render(directives) + {message, rendered} = SlackProcessor.render(directives) assert text == message cond do is_binary(attachments) -> @@ -56,4 +64,5 @@ defmodule Cog.TemplateCase do |> Path.join("#{template_name}#{New.extension}") |> File.read! end + end diff --git a/test/test_helper.exs b/test/test_helper.exs index 6cb40b4d..6fc6fea8 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,7 +1,7 @@ Application.ensure_all_started(:cog) exclude_slack = System.get_env("TEST_SLACK") == nil -exclude_relay = System.get_env("TEST_RELAY") == nil +exclude_hipchat = System.get_env("TEST_HIPCHAT") == nil Ecto.Adapters.SQL.Sandbox.mode(Cog.Repo, :manual) @@ -9,4 +9,4 @@ ExVCR.Config.cassette_library_dir("test/fixtures/cassettes") ExVCR.Config.filter_sensitive_data("token=[^&]+", "token=xoxb-filtered-token") ExVCR.Config.filter_sensitive_data("Bearer .*", "Bearer filtered-token") -ExUnit.start(exclude: [slack: exclude_slack, relay: exclude_relay]) +ExUnit.start(exclude: [slack: exclude_slack, hipchat: exclude_hipchat])