diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb
index 2bcff541cc0967271ae81cf04aedc661d2cd892f..608754f3035882290fe5174f5e0c0a99a70c0e2f 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/chat_slash_commands_service.rb
@@ -28,20 +28,24 @@ class ChatSlashCommandsService < Service
   end
 
   def trigger(params)
-    return unless valid_token?(params[:token])
+    return access_presenter unless valid_token?(params[:token])
 
     user = find_chat_user(params)
-    unless user
+
+    if user
+      Gitlab::ChatCommands::Command.new(project, user, params).execute
+    else
       url = authorize_chat_name_url(params)
-      return presenter.authorize_chat_name(url)
+      access_presenter(url).authorize
     end
-
-    Gitlab::ChatCommands::Command.new(project, user,
-      params).execute
   end
 
   private
 
+  def access_presenter(url = nil)
+    Gitlab::ChatCommands::Presenters::Access.new(url)
+  end
+
   def find_chat_user(params)
     ChatNames::FindUserService.new(self, params).execute
   end
@@ -49,8 +53,4 @@ class ChatSlashCommandsService < Service
   def authorize_chat_name_url(params)
     ChatNames::AuthorizeUserService.new(self, params).execute
   end
-
-  def presenter
-    Gitlab::ChatCommands::Presenter.new
-  end
 end
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb
index 4fe53ce93a99791d3be0a9e8fea635e81db5c451..25da8474e95a0f7c43c507b3f18be238ef849c63 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/chat_commands/base_command.rb
@@ -42,10 +42,6 @@ module Gitlab
       def find_by_iid(iid)
         collection.find_by(iid: iid)
       end
-
-      def presenter
-        Gitlab::ChatCommands::Presenter.new
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 145086755e4a2240ef123b9097fecd88d75fdc6a..ac7ee868402005374d04ffacc7530b3ffa004d34 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -13,9 +13,9 @@ module Gitlab
 
         if command
           if command.allowed?(project, current_user)
-            present command.new(project, current_user, params).execute(match)
+            command.new(project, current_user, params).execute(match)
           else
-            access_denied
+            Gitlab::ChatCommands::Presenters::Access.new.access_denied
           end
         else
           help(help_messages)
@@ -25,7 +25,7 @@ module Gitlab
       def match_command
         match = nil
         service = available_commands.find do |klass|
-          match = klass.match(command)
+          match = klass.match(params[:text])
         end
 
         [service, match]
@@ -42,22 +42,6 @@ module Gitlab
           klass.available?(project)
         end
       end
-
-      def command
-        params[:text]
-      end
-
-      def help(messages)
-        presenter.help(messages, params[:command])
-      end
-
-      def access_denied
-        presenter.access_denied
-      end
-
-      def present(resource)
-        presenter.present(resource)
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
index 7127d2f6d047cac40f779fb609c3e20521b417d2..458d90f84e8f066535b4b2790f92d82e7cd73b2c 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -1,8 +1,6 @@
 module Gitlab
   module ChatCommands
     class Deploy < BaseCommand
-      include Gitlab::Routing.url_helpers
-
       def self.match(text)
         /\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
       end
@@ -24,35 +22,29 @@ module Gitlab
         to = match[:to]
 
         actions = find_actions(from, to)
-        return unless actions.present?
 
-        if actions.one?
-          play!(from, to, actions.first)
+        if actions.none?
+          Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
+        elsif actions.one?
+          action = play!(from, to, actions.first)
+          Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
         else
-          Result.new(:error, 'Too many actions defined')
+          Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
         end
       end
 
       private
 
       def play!(from, to, action)
-        new_action = action.play(current_user)
-
-        Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.")
+        action.play(current_user)
       end
 
       def find_actions(from, to)
         environment = project.environments.find_by(name: from)
-        return unless environment
+        return [] unless environment
 
         environment.actions_for(to).select(&:starts_environment?)
       end
-
-      def url(subject)
-        polymorphic_url(
-          [subject.project.namespace.becomes(Namespace), subject.project, subject]
-        )
-      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_create.rb
index cefb6775db854becff484cc09f75073489a1611f..a06f13b0f72cdb0fe25358face8186770e57d993 100644
--- a/lib/gitlab/chat_commands/issue_create.rb
+++ b/lib/gitlab/chat_commands/issue_create.rb
@@ -2,7 +2,7 @@ module Gitlab
   module ChatCommands
     class IssueCreate < IssueCommand
       def self.match(text)
-        # we can not match \n with the dot by passing the m modifier as than 
+        # we can not match \n with the dot by passing the m modifier as than
         # the title and description are not seperated
         /\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text)
       end
@@ -19,8 +19,24 @@ module Gitlab
         title = match[:title]
         description = match[:description].to_s.rstrip
 
+        issue = create_issue(title: title, description: description)
+
+        if issue.errors.any?
+          presenter(issue).display_errors
+        else
+          presenter(issue).present
+        end
+      end
+
+      private
+
+      def create_issue(title:, description:)
         Issues::CreateService.new(project, current_user, title: title, description: description).execute
       end
+
+      def presenter(issue)
+        Gitlab::ChatCommands::Presenters::ShowIssue.new(issue)
+      end
     end
   end
 end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb
index 51bf80c800b73f4fe09292859b8f68e91b16ac29..e2d3a0f466ab2e8fb72060fb28702ffd25cac262 100644
--- a/lib/gitlab/chat_commands/issue_search.rb
+++ b/lib/gitlab/chat_commands/issue_search.rb
@@ -10,7 +10,15 @@ module Gitlab
       end
 
       def execute(match)
-        collection.search(match[:query]).limit(QUERY_LIMIT)
+        issues = collection.search(match[:query]).limit(QUERY_LIMIT)
+
+        if issues.none?
+          Presenters::Access.new(issues).not_found
+        elsif issues.one?
+          Presenters::ShowIssue.new(issues.first).present
+        else
+          Presenters::ListIssues.new(issues).present
+        end
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb
index 2a45d49cf6b30824f5672ccfcebb1e9172ced818..9f3e1b9a64b7bd8b7161a5e0c99ce5d24d233a28 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/chat_commands/issue_show.rb
@@ -10,7 +10,13 @@ module Gitlab
       end
 
       def execute(match)
-        find_by_iid(match[:iid])
+        issue = find_by_iid(match[:iid])
+
+        if issue
+          Gitlab::ChatCommands::Presenters::ShowIssue.new(issue).present
+        else
+          Gitlab::ChatCommands::Presenters::Access.new.not_found
+        end
       end
     end
   end
diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb
deleted file mode 100644
index 8930a21f4065b231029d2ee986a2676211413c60..0000000000000000000000000000000000000000
--- a/lib/gitlab/chat_commands/presenter.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-module Gitlab
-  module ChatCommands
-    class Presenter
-      include Gitlab::Routing
-
-      def authorize_chat_name(url)
-        message = if url
-                    ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})."
-                  else
-                    ":sweat_smile: Couldn't identify you, nor can I autorize you!"
-                  end
-
-        ephemeral_response(message)
-      end
-
-      def help(commands, trigger)
-        if commands.none?
-          ephemeral_response("No commands configured")
-        else
-          commands.map! { |command| "#{trigger} #{command}" }
-          message = header_with_list("Available commands", commands)
-
-          ephemeral_response(message)
-        end
-      end
-
-      def present(subject)
-        return not_found unless subject
-
-        if subject.is_a?(Gitlab::ChatCommands::Result)
-          show_result(subject)
-        elsif subject.respond_to?(:count)
-          if subject.none?
-            not_found
-          elsif subject.one?
-            single_resource(subject.first)
-          else
-            multiple_resources(subject)
-          end
-        else
-          single_resource(subject)
-        end
-      end
-
-      def access_denied
-        ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
-      end
-
-      private
-
-      def show_result(result)
-        case result.type
-        when :success
-          in_channel_response(result.message)
-        else
-          ephemeral_response(result.message)
-        end
-      end
-
-      def not_found
-        ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
-      end
-
-      def single_resource(resource)
-        return error(resource) if resource.errors.any? || !resource.persisted?
-
-        message = "#{title(resource)}:"
-        message << "\n\n#{resource.description}" if resource.try(:description)
-
-        in_channel_response(message)
-      end
-
-      def multiple_resources(resources)
-        titles = resources.map { |resource| title(resource) }
-
-        message = header_with_list("Multiple results were found:", titles)
-
-        ephemeral_response(message)
-      end
-
-      def error(resource)
-        message = header_with_list("The action was not successful, because:", resource.errors.messages)
-
-        ephemeral_response(message)
-      end
-
-      def title(resource)
-        reference = resource.try(:to_reference) || resource.try(:id)
-        title = resource.try(:title) || resource.try(:name)
-
-        "[#{reference} #{title}](#{url(resource)})"
-      end
-
-      def header_with_list(header, items)
-        message = [header]
-
-        items.each do |item|
-          message << "- #{item}"
-        end
-
-        message.join("\n")
-      end
-
-      def url(resource)
-        url_for(
-          [
-            resource.project.namespace.becomes(Namespace),
-            resource.project,
-            resource
-          ]
-        )
-      end
-
-      def ephemeral_response(message)
-        {
-          response_type: :ephemeral,
-          text: message,
-          status: 200
-        }
-      end
-
-      def in_channel_response(message)
-        {
-          response_type: :in_channel,
-          text: message,
-          status: 200
-        }
-      end
-    end
-  end
-end
diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6d18d745608645f03d2bfdfe20a03fd70e1762b5
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/access.rb
@@ -0,0 +1,22 @@
+module Gitlab::ChatCommands::Presenters
+  class Access < Gitlab::ChatCommands::Presenters::Base
+    def access_denied
+      ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
+    end
+
+    def not_found
+      ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
+    end
+
+    def authorize
+      message =
+        if @resource
+          ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})."
+        else
+          ":sweat_smile: Couldn't identify you, nor can I autorize you!"
+        end
+
+      ephemeral_response(text: message)
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0897025d85fab653f1f79946cdf0ba92ed6fd75f
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/base.rb
@@ -0,0 +1,73 @@
+module Gitlab::ChatCommands::Presenters
+  class Base
+    include Gitlab::Routing.url_helpers
+
+    def initialize(resource = nil)
+      @resource = resource
+    end
+
+    def display_errors
+      message = header_with_list("The action was not successful, because:", @resource.errors.full_messages)
+
+      ephemeral_response(text: message)
+    end
+
+    private
+
+    def header_with_list(header, items)
+      message = [header]
+
+      items.each do |item|
+        message << "- #{item}"
+      end
+
+      message.join("\n")
+    end
+
+    def ephemeral_response(message)
+      response = {
+        response_type: :ephemeral,
+        status: 200
+      }.merge(message)
+
+      format_response(response)
+    end
+
+    def in_channel_response(message)
+      response = {
+        response_type: :in_channel,
+        status: 200
+      }.merge(message)
+
+      format_response(response)
+    end
+
+    def format_response(response)
+      response[:text] = format(response[:text]) if response.has_key?(:text)
+
+      if response.has_key?(:attachments)
+        response[:attachments].each do |attachment|
+          attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext]
+          attachment[:text] = format(attachment[:text]) if attachment[:text]
+        end
+      end
+
+      response
+    end
+
+    # Convert Markdown to slacks format
+    def format(string)
+      Slack::Notifier::LinkFormatter.format(string)
+    end
+
+    def resource_url
+      url_for(
+        [
+          @resource.project.namespace.becomes(Namespace),
+          @resource.project,
+          @resource
+        ]
+      )
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4f6333812ff28626550cecca1e4364c4b59e7049
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/deploy.rb
@@ -0,0 +1,24 @@
+module Gitlab::ChatCommands::Presenters
+  class Deploy < Gitlab::ChatCommands::Presenters::Base
+    def present(from, to)
+      message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})."
+      in_channel_response(text: message)
+    end
+
+    def no_actions
+      ephemeral_response(text: "No action found to be executed")
+    end
+
+    def too_many_actions
+      ephemeral_response(text: "Too many actions defined")
+    end
+
+    private
+
+    def resource_url
+      polymorphic_url(
+        [ @resource.project.namespace.becomes(Namespace), @resource.project, @resource]
+      )
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9623387f1887157837c5dc35372edbd6baaa3399
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issuable.rb
@@ -0,0 +1,33 @@
+module Gitlab::ChatCommands::Presenters
+  class Issuable < Gitlab::ChatCommands::Presenters::Base
+    private
+
+    def project
+      @resource.project
+    end
+
+    def author
+      @resource.author
+    end
+
+    def fields
+      [
+        {
+          title: "Assignee",
+          value: @resource.assignee ? @resource.assignee.name : "_None_",
+          short: true
+        },
+        {
+          title: "Milestone",
+          value: @resource.milestone ? @resource.milestone.title : "_None_",
+          short: true
+        },
+        {
+          title: "Labels",
+          value: @resource.labels.any? ? @resource.label_names : "_None_",
+          short: true
+        }
+      ]
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/list_issues.rb b/lib/gitlab/chat_commands/presenters/list_issues.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5a7b3fca5c22a39b7d40cc3219f0bb2db48e8892
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/list_issues.rb
@@ -0,0 +1,32 @@
+module Gitlab::ChatCommands::Presenters
+  class ListIssues < Gitlab::ChatCommands::Presenters::Base
+    def present
+      ephemeral_response(text: "Here are the issues I found:", attachments: attachments)
+    end
+
+    private
+
+    def attachments
+      @resource.map do |issue|
+        state = issue.open? ? "Open" : "Closed"
+
+        {
+          fallback: "Issue #{issue.to_reference}: #{issue.title}",
+          color: "#d22852",
+          text: "[#{issue.to_reference}](#{url_for([namespace, project, issue])}) 路 #{issue.title} (#{state})",
+          mrkdwn_in: [
+            "text"
+          ]
+        }
+      end
+    end
+
+    def project
+      @project ||= @resource.first.project
+    end
+
+    def namespace
+      @namespace ||= project.namespace.becomes(Namespace)
+    end
+  end
+end
diff --git a/lib/gitlab/chat_commands/presenters/show_issue.rb b/lib/gitlab/chat_commands/presenters/show_issue.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2a89c30b972cd1e9941af9e10e639b6a22823e87
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/show_issue.rb
@@ -0,0 +1,38 @@
+module Gitlab::ChatCommands::Presenters
+  class ShowIssue < Gitlab::ChatCommands::Presenters::Issuable
+    def present
+      in_channel_response(show_issue)
+    end
+
+    private
+
+    def show_issue
+      {
+        attachments: [
+          {
+            title:        @resource.title,
+            title_link:   resource_url,
+            author_name:  author.name,
+            author_icon:  author.avatar_url,
+            fallback:     "#{@resource.to_reference}: #{@resource.title}",
+            text:         text,
+            fields:       fields,
+            mrkdwn_in: [
+              :title,
+              :text
+            ]
+          }
+        ]
+      }
+    end
+
+    def text
+      message = ""
+      message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero?
+      message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero?
+      message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero?
+
+      message
+    end
+  end
+end
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
deleted file mode 100644
index ec2903b7ec6c1daf7bdeea3320155da2c8149c81..0000000000000000000000000000000000000000
--- a/lib/mattermost/client.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-module Mattermost
-  class ClientError < Mattermost::Error; end
-
-  class Client
-    attr_reader :user
-
-    def initialize(user)
-      @user = user
-    end
-
-    private
-
-    def with_session(&blk)
-      Mattermost::Session.new(user).with_session(&blk)
-    end
-
-    def json_get(path, options = {})
-      with_session do |session|
-        json_response session.get(path, options)
-      end
-    end
-
-    def json_post(path, options = {})
-      with_session do |session|
-        json_response session.post(path, options)
-      end
-    end
-
-    def json_response(response)
-      json_response = JSON.parse(response.body)
-
-      unless response.success?
-        raise Mattermost::ClientError.new(json_response['message'] || 'Undefined error')
-      end
-
-      json_response
-    rescue JSON::JSONError
-      raise Mattermost::ClientError.new('Cannot parse response')
-    end
-  end
-end
diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb
deleted file mode 100644
index d1e4bb0eccf83cc0ff4d1af3f4be6e1edbd19ee3..0000000000000000000000000000000000000000
--- a/lib/mattermost/command.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-module Mattermost
-  class Command < Client
-    def create(params)
-      response = json_post("/api/v3/teams/#{params[:team_id]}/commands/create",
-        body: params.to_json)
-
-      response['token']
-    end
-  end
-end
diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb
deleted file mode 100644
index 014df175be09d87b0a3fd218363beaf6ca41cd5c..0000000000000000000000000000000000000000
--- a/lib/mattermost/error.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-module Mattermost
-  class Error < StandardError; end
-end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
deleted file mode 100644
index 377cb7b10211f4b8101d923d6fb99c0f0500cb32..0000000000000000000000000000000000000000
--- a/lib/mattermost/session.rb
+++ /dev/null
@@ -1,160 +0,0 @@
-module Mattermost
-  class NoSessionError < Mattermost::Error
-    def message
-      'No session could be set up, is Mattermost configured with Single Sign On?'
-    end
-  end
-
-  class ConnectionError < Mattermost::Error; end
-
-  # This class' prime objective is to obtain a session token on a Mattermost
-  # instance with SSO configured where this GitLab instance is the provider.
-  #
-  # The process depends on OAuth, but skips a step in the authentication cycle.
-  # For example, usually a user would click the 'login in GitLab' button on
-  # Mattermost, which would yield a 302 status code and redirects you to GitLab
-  # to approve the use of your account on Mattermost. Which would trigger a
-  # callback so Mattermost knows this request is approved and gets the required
-  # data to create the user account etc.
-  #
-  # This class however skips the button click, and also the approval phase to
-  # speed up the process and keep it without manual action and get a session
-  # going.
-  class Session
-    include Doorkeeper::Helpers::Controller
-    include HTTParty
-
-    LEASE_TIMEOUT = 60
-
-    base_uri Settings.mattermost.host
-
-    attr_accessor :current_resource_owner, :token
-
-    def initialize(current_user)
-      @current_resource_owner = current_user
-    end
-
-    def with_session
-      with_lease do
-        raise Mattermost::NoSessionError unless create
-
-        begin
-          yield self
-        rescue Errno::ECONNREFUSED
-          raise Mattermost::NoSessionError
-        ensure
-          destroy
-        end
-      end
-    end
-
-    # Next methods are needed for Doorkeeper
-    def pre_auth
-      @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(
-        Doorkeeper.configuration, server.client_via_uid, params)
-    end
-
-    def authorization
-      @authorization ||= strategy.request
-    end
-
-    def strategy
-      @strategy ||= server.authorization_request(pre_auth.response_type)
-    end
-
-    def request
-      @request ||= OpenStruct.new(parameters: params)
-    end
-
-    def params
-      Rack::Utils.parse_query(oauth_uri.query).symbolize_keys
-    end
-
-    def get(path, options = {})
-      handle_exceptions do
-        self.class.get(path, options.merge(headers: @headers))
-      end
-    end
-
-    def post(path, options = {})
-      handle_exceptions do
-        self.class.post(path, options.merge(headers: @headers))
-      end
-    end
-
-    private
-
-    def create
-      return unless oauth_uri
-      return unless token_uri
-
-      @token = request_token
-      @headers = {
-        Authorization: "Bearer #{@token}"
-      }
-
-      @token
-    end
-
-    def destroy
-      post('/api/v3/users/logout')
-    end
-
-    def oauth_uri
-      return @oauth_uri if defined?(@oauth_uri)
-
-      @oauth_uri = nil
-
-      response = get("/api/v3/oauth/gitlab/login", follow_redirects: false)
-      return unless 300 <= response.code && response.code < 400
-
-      redirect_uri = response.headers['location']
-      return unless redirect_uri
-
-      @oauth_uri = URI.parse(redirect_uri)
-    end
-
-    def token_uri
-      @token_uri ||=
-        if oauth_uri
-          authorization.authorize.redirect_uri if pre_auth.authorizable?
-        end
-    end
-
-    def request_token
-      response = get(token_uri, follow_redirects: false)
-
-      if 200 <= response.code && response.code < 400
-        response.headers['token']
-      end
-    end
-
-    def with_lease
-      lease_uuid = lease_try_obtain
-      raise NoSessionError unless lease_uuid
-
-      begin
-        yield
-      ensure
-        Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid)
-      end
-    end
-
-    def lease_key
-      "mattermost:session"
-    end
-
-    def lease_try_obtain
-      lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
-      lease.try_obtain
-    end
-
-    def handle_exceptions
-      yield
-    rescue HTTParty::Error => e
-      raise Mattermost::ConnectionError.new(e.message)
-    rescue Errno::ECONNREFUSED
-      raise Mattermost::ConnectionError.new(e.message)
-    end
-  end
-end
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
deleted file mode 100644
index 784eca6ab5a61e5f048515ac296873f970039ba7..0000000000000000000000000000000000000000
--- a/lib/mattermost/team.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Mattermost
-  class Team < Client
-    def all
-      json_get('/api/v3/teams/all')
-    end
-  end
-end
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index a2d84977f585780617d02c101d7d9fbd39f1ed57..b634df52b681db59b66853fbdb9cf236a1aadfc9 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -5,19 +5,7 @@ describe Gitlab::ChatCommands::Command, service: true do
   let(:user) { create(:user) }
 
   describe '#execute' do
-    subject do
-      described_class.new(project, user, params).execute
-    end
-
-    context 'when no command is available' do
-      let(:params) { { text: 'issue show 1' } }
-      let(:project) { create(:project, has_external_issue_tracker: true) }
-
-      it 'displays 404 messages' do
-        expect(subject[:response_type]).to be(:ephemeral)
-        expect(subject[:text]).to start_with('404 not found')
-      end
-    end
+    subject { described_class.new(project, user, params).execute }
 
     context 'when an unknown command is triggered' do
       let(:params) { { command: '/gitlab', text: "unknown command 123" } }
@@ -34,47 +22,7 @@ describe Gitlab::ChatCommands::Command, service: true do
 
       it 'rejects the actions' do
         expect(subject[:response_type]).to be(:ephemeral)
-        expect(subject[:text]).to start_with('Whoops! That action is not allowed')
-      end
-    end
-
-    context 'issue is successfully created' do
-      let(:params) { { text: "issue create my new issue" } }
-
-      before do
-        project.team << [user, :master]
-      end
-
-      it 'presents the issue' do
-        expect(subject[:text]).to match("my new issue")
-      end
-
-      it 'shows a link to the new issue' do
-        expect(subject[:text]).to match(/\/issues\/\d+/)
-      end
-    end
-
-    context 'searching for an issue' do
-      let(:params) { { text: 'issue search find me' } }
-      let!(:issue) { create(:issue, project: project, title: 'find me') }
-
-      before do
-        project.team << [user, :master]
-      end
-
-      context 'a single issue is found' do
-        it 'presents the issue' do
-          expect(subject[:text]).to match(issue.title)
-        end
-      end
-
-      context 'multiple issues found' do
-        let!(:issue2) { create(:issue, project: project, title: "someone find me") }
-
-        it 'shows a link to the new issue' do
-          expect(subject[:text]).to match(issue.title)
-          expect(subject[:text]).to match(issue2.title)
-        end
+        expect(subject[:text]).to start_with('Whoops! This action is not allowed')
       end
     end
 
@@ -90,7 +38,7 @@ describe Gitlab::ChatCommands::Command, service: true do
       context 'and user can not create deployment' do
         it 'returns action' do
           expect(subject[:response_type]).to be(:ephemeral)
-          expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+          expect(subject[:text]).to start_with('Whoops! This action is not allowed')
         end
       end
 
@@ -100,7 +48,7 @@ describe Gitlab::ChatCommands::Command, service: true do
         end
 
         it 'returns action' do
-          expect(subject[:text]).to include('Deployment from staging to production started.')
+          expect(subject[:text]).to include('Deployment started from staging to production')
           expect(subject[:response_type]).to be(:in_channel)
         end
 
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index bd8099c92da997c1e6a1d2a39b53a83155e9f0a4..b3358a321618d5aa22f3709dd87be5a9bc6f788b 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
     end
 
     context 'if no environment is defined' do
-      it 'returns nil' do
-        expect(subject).to be_nil
+      it 'does not execute an action' do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to eq("No action found to be executed")
       end
     end
 
@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
       let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
 
       context 'without actions' do
-        it 'returns nil' do
-          expect(subject).to be_nil
+        it 'does not execute an action' do
+          expect(subject[:response_type]).to be(:ephemeral)
+          expect(subject[:text]).to eq("No action found to be executed")
         end
       end
 
@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
         end
 
         it 'returns success result' do
-          expect(subject.type).to eq(:success)
-          expect(subject.message).to include('Deployment from staging to production started')
+          expect(subject[:response_type]).to be(:in_channel)
+          expect(subject[:text]).to start_with('Deployment started from staging to production')
         end
 
         context 'when duplicate action exists' do
@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
           end
 
           it 'returns error' do
-            expect(subject.type).to eq(:error)
-            expect(subject.message).to include('Too many actions defined')
+            expect(subject[:response_type]).to be(:ephemeral)
+            expect(subject[:text]).to eq('Too many actions defined')
           end
         end
 
@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
                    name: 'teardown', environment: 'production')
           end
 
-          it 'returns success result' do
-            expect(subject.type).to eq(:success)
-            expect(subject.message).to include('Deployment from staging to production started')
+          it 'returns the success message' do
+            expect(subject[:response_type]).to be(:in_channel)
+            expect(subject[:text]).to start_with('Deployment started from staging to production')
           end
         end
       end
diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_create_spec.rb
index 6c71e79ff6d163fb12fdd96e0ec095f3cb6939d0..0f84b19a5a40927a09d3851616caac56455383ca 100644
--- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_create_spec.rb
@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
       it 'creates the issue' do
         expect { subject }.to change { project.issues.count }.by(1)
 
-        expect(subject.title).to eq('bird is the word')
+        expect(subject[:response_type]).to be(:in_channel)
       end
     end
 
@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
         expect { subject }.to change { project.issues.count }.by(1)
       end
     end
+
+    context 'issue cannot be created' do
+      let!(:issue)  { create(:issue, project: project, title: 'bird is the word') }
+      let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
+
+      it 'displays the errors' do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("- Title is too long")
+      end
+    end
   end
 
   describe '.match' do
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
index 24c06a967fa4ed8f30fe13fca196e283eaced5f7..04d10ad52a1921640f3d59cc4cf53dcd31a05e5b 100644
--- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
 
 describe Gitlab::ChatCommands::IssueSearch, service: true do
   describe '#execute' do
-    let!(:issue) { create(:issue, title: 'find me') }
+    let!(:issue) { create(:issue, project: project, title: 'find me') }
     let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
-    let(:project) { issue.project }
+    let(:project) { create(:empty_project) }
     let(:user) { issue.author }
     let(:regex_match) { described_class.match("issue search find") }
 
@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
 
     context 'when the user has no access' do
       it 'only returns the open issues' do
-        expect(subject).not_to include(confidential)
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("not found")
       end
     end
 
@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
       end
 
       it 'returns all results' do
-        expect(subject).to include(confidential, issue)
+        expect(subject).to have_key(:attachments)
+        expect(subject[:text]).to match("Here are the issues I found:")
       end
     end
 
     context 'without hits on the query' do
       it 'returns an empty collection' do
-        expect(subject).to be_empty
+        expect(subject[:text]).to match("not found")
       end
     end
   end
diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
index 2eab73e49e5deb8224795d1472e5b3f3661c56b1..89932c395c60b6eec2d0277c43557b6a646122a7 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
 
 describe Gitlab::ChatCommands::IssueShow, service: true do
   describe '#execute' do
-    let(:issue) { create(:issue) }
-    let(:project) { issue.project }
+    let(:issue) { create(:issue, project: project) }
+    let(:project) { create(:empty_project) }
     let(:user) { issue.author }
     let(:regex_match) { described_class.match("issue show #{issue.iid}") }
 
@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
     end
 
     context 'the issue exists' do
+      let(:title) { subject[:attachments].first[:title] }
+
       it 'returns the issue' do
-        expect(subject.iid).to be issue.iid
+        expect(subject[:response_type]).to be(:in_channel)
+        expect(title).to eq(issue.title)
       end
 
       context 'when its reference is given' do
         let(:regex_match) { described_class.match("issue show #{issue.to_reference}") }
 
         it 'shows the issue' do
-          expect(subject.iid).to be issue.iid
+          expect(subject[:response_type]).to be(:in_channel)
+          expect(title).to eq(issue.title)
         end
       end
     end
@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
     context 'the issue does not exist' do
       let(:regex_match) { described_class.match("issue show 2343242") }
 
-      it "returns nil" do
-        expect(subject).to be_nil
+      it "returns not found" do
+        expect(subject[:response_type]).to be(:ephemeral)
+        expect(subject[:text]).to match("not found")
       end
     end
   end
 
-  describe 'self.match' do
+  describe '.match' do
     it 'matches the iid' do
       match = described_class.match("issue show 123")
 
       expect(match[:iid]).to eq("123")
     end
+
+    it 'accepts a reference' do
+      match = described_class.match("issue show #{Issue.reference_prefix}123")
+
+      expect(match[:iid]).to eq("123")
+    end
   end
 end
diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ae41d75ab0c2654df66e2f946d7a43ad0c6bf5f9
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Access do
+  describe '#access_denied' do
+    subject { described_class.new.access_denied }
+
+    it { is_expected.to be_a(Hash) }
+
+    it 'displays an error message' do
+      expect(subject[:text]).to match("is not allowed")
+      expect(subject[:response_type]).to be(:ephemeral)
+    end
+  end
+
+  describe '#not_found' do
+    subject { described_class.new.not_found }
+
+    it { is_expected.to be_a(Hash) }
+
+    it 'tells the user the resource was not found' do
+      expect(subject[:text]).to match("not found!")
+      expect(subject[:response_type]).to be(:ephemeral)
+    end
+  end
+
+  describe '#authorize' do
+    context 'with an authorization URL' do
+      subject { described_class.new('http://authorize.me').authorize }
+
+      it { is_expected.to be_a(Hash) }
+
+      it 'tells the user to authorize' do
+        expect(subject[:text]).to match("connect your GitLab account")
+        expect(subject[:response_type]).to be(:ephemeral)
+      end
+    end
+
+    context 'without authorization url' do
+      subject { described_class.new.authorize }
+
+      it { is_expected.to be_a(Hash) }
+
+      it 'tells the user to authorize' do
+        expect(subject[:text]).to match("Couldn't identify you")
+        expect(subject[:response_type]).to be(:ephemeral)
+      end
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1c48c727e3079af75a751d6e725eda67d6c0ccbe
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Deploy do
+  let(:build) { create(:ci_build) }
+
+  describe '#present' do
+    subject { described_class.new(build).present('staging', 'prod') }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'messages the channel of the deploy' do
+      expect(subject[:response_type]).to be(:in_channel)
+      expect(subject[:text]).to start_with("Deployment started from staging to prod")
+    end
+  end
+
+  describe '#no_actions' do
+    subject { described_class.new(nil).no_actions }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'tells the user there is no action' do
+      expect(subject[:response_type]).to be(:ephemeral)
+      expect(subject[:text]).to eq("No action found to be executed")
+    end
+  end
+
+  describe '#too_many_actions' do
+    subject { described_class.new(nil).too_many_actions }
+
+    it { is_expected.to have_key(:text) }
+    it { is_expected.to have_key(:response_type) }
+    it { is_expected.to have_key(:status) }
+    it { is_expected.not_to have_key(:attachments) }
+
+    it 'tells the user there is no action' do
+      expect(subject[:response_type]).to be(:ephemeral)
+      expect(subject[:text]).to eq("Too many actions defined")
+    end
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1852395fc97c41811f87e64bc02873f116a5be94
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/list_issues_spec.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::ListIssues do
+  let(:project) { create(:empty_project) }
+  let(:message) { subject[:text] }
+  let(:issue) { project.issues.first }
+
+  before { create_list(:issue, 2, project: project) }
+
+  subject { described_class.new(project.issues).present }
+
+  it do
+    is_expected.to have_key(:text)
+    is_expected.to have_key(:status)
+    is_expected.to have_key(:response_type)
+    is_expected.to have_key(:attachments)
+  end
+
+  it 'shows a list of results' do
+    expect(subject[:response_type]).to be(:ephemeral)
+
+    expect(message).to start_with("Here are the issues I found")
+  end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..13a318fe68082938d1bf8327f5fbdba30ff04d77
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/show_issue_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::ShowIssue do
+  let(:project) { create(:empty_project) }
+  let(:issue) { create(:issue, project: project) }
+  let(:attachment) { subject[:attachments].first }
+
+  subject { described_class.new(issue).present }
+
+  it { is_expected.to be_a(Hash) }
+
+  it 'shows the issue' do
+    expect(subject[:response_type]).to be(:in_channel)
+    expect(subject).to have_key(:attachments)
+    expect(attachment[:title]).to eq(issue.title)
+  end
+
+  context 'with upvotes' do
+    before do
+      create(:award_emoji, :upvote, awardable: issue)
+    end
+
+    it 'shows the upvote count' do
+      expect(attachment[:text]).to start_with(":+1: 1")
+    end
+  end
+end
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
deleted file mode 100644
index dc11a414717c2afe188884290c4af7b7b7e1a74c..0000000000000000000000000000000000000000
--- a/spec/lib/mattermost/client_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-require 'spec_helper'
-
-describe Mattermost::Client do
-  let(:user) { build(:user) }
-
-  subject { described_class.new(user) }
-
-  context 'JSON parse error' do
-    before do
-      Struct.new("Request", :body, :success?)
-    end
-
-    it 'yields an error on malformed JSON' do
-      bad_json = Struct::Request.new("I'm not json", true)
-      expect { subject.send(:json_response, bad_json) }.to raise_error(Mattermost::ClientError)
-    end
-
-    it 'shows a client error if the request was unsuccessful' do
-      bad_request = Struct::Request.new("true", false)
-
-      expect { subject.send(:json_response, bad_request) }.to raise_error(Mattermost::ClientError)
-    end
-  end
-end
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
deleted file mode 100644
index 5ccf11008980a827f48a146fe31f22f92a87b9b1..0000000000000000000000000000000000000000
--- a/spec/lib/mattermost/command_spec.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-require 'spec_helper'
-
-describe Mattermost::Command do
-  let(:params) { { 'token' => 'token', team_id: 'abc' } }
-
-  before do
-    Mattermost::Session.base_uri('http://mattermost.example.com')
-
-    allow_any_instance_of(Mattermost::Client).to receive(:with_session).
-      and_yield(Mattermost::Session.new(nil))
-  end
-
-  describe '#create' do
-    let(:params) do
-      { team_id: 'abc',
-        trigger: 'gitlab'
-      }
-    end
-
-    subject { described_class.new(nil).create(params) }
-
-    context 'for valid trigger word' do
-      before do
-        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-          with(body: {
-            team_id: 'abc',
-            trigger: 'gitlab' }.to_json).
-          to_return(
-            status: 200,
-            headers: { 'Content-Type' => 'application/json' },
-            body: { token: 'token' }.to_json
-          )
-      end
-
-      it 'returns a token' do
-        is_expected.to eq('token')
-      end
-    end
-
-    context 'for error message' do
-      before do
-        stub_request(:post, 'http://mattermost.example.com/api/v3/teams/abc/commands/create').
-          to_return(
-            status: 500,
-            headers: { 'Content-Type' => 'application/json' },
-            body: {
-              id: 'api.command.duplicate_trigger.app_error',
-              message: 'This trigger word is already in use. Please choose another word.',
-              detailed_error: '',
-              request_id: 'obc374man7bx5r3dbc1q5qhf3r',
-              status_code: 500
-            }.to_json
-          )
-      end
-
-      it 'raises an error with message' do
-        expect { subject }.to raise_error(Mattermost::Error, 'This trigger word is already in use. Please choose another word.')
-      end
-    end
-  end
-end
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
deleted file mode 100644
index 74d12e3718105f58afdc828b83a3196220d4c0fd..0000000000000000000000000000000000000000
--- a/spec/lib/mattermost/session_spec.rb
+++ /dev/null
@@ -1,123 +0,0 @@
-require 'spec_helper'
-
-describe Mattermost::Session, type: :request do
-  let(:user) { create(:user) }
-
-  let(:gitlab_url) { "http://gitlab.com" }
-  let(:mattermost_url) { "http://mattermost.com" }
-
-  subject { described_class.new(user) }
-
-  # Needed for doorkeeper to function
-  it { is_expected.to respond_to(:current_resource_owner) }
-  it { is_expected.to respond_to(:request) }
-  it { is_expected.to respond_to(:authorization) }
-  it { is_expected.to respond_to(:strategy) }
-
-  before do
-    described_class.base_uri(mattermost_url)
-  end
-
-  describe '#with session' do
-    let(:location) { 'http://location.tld' }
-    let!(:stub) do
-      WebMock.stub_request(:get, "#{mattermost_url}/api/v3/oauth/gitlab/login").
-        to_return(headers: { 'location' => location }, status: 307)
-    end
-
-    context 'without oauth uri' do
-      it 'makes a request to the oauth uri' do
-        expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
-      end
-    end
-
-    context 'with oauth_uri' do
-      let!(:doorkeeper) do
-        Doorkeeper::Application.create(
-          name: "GitLab Mattermost",
-          redirect_uri: "#{mattermost_url}/signup/gitlab/complete\n#{mattermost_url}/login/gitlab/complete",
-          scopes: "")
-      end
-
-      context 'without token_uri' do
-        it 'can not create a session' do
-          expect do
-            subject.with_session
-          end.to raise_error(Mattermost::NoSessionError)
-        end
-      end
-
-      context 'with token_uri' do
-        let(:state) { "state" }
-        let(:params) do
-          { response_type: "code",
-            client_id: doorkeeper.uid,
-            redirect_uri: "#{mattermost_url}/signup/gitlab/complete",
-            state: state }
-        end
-        let(:location) do
-          "#{gitlab_url}/oauth/authorize?#{URI.encode_www_form(params)}"
-        end
-
-        before do
-          WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete").
-            with(query: hash_including({ 'state' => state })).
-            to_return do |request|
-              post "/oauth/token",
-                client_id: doorkeeper.uid,
-                client_secret: doorkeeper.secret,
-                redirect_uri: params[:redirect_uri],
-                grant_type: 'authorization_code',
-                code: request.uri.query_values['code']
-
-              if response.status == 200
-                { headers: { 'token' => 'thisworksnow' }, status: 202 }
-              end
-            end
-
-          WebMock.stub_request(:post, "#{mattermost_url}/api/v3/users/logout").
-            to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
-        end
-
-        it 'can setup a session' do
-          subject.with_session do |session|
-          end
-
-          expect(subject.token).not_to be_nil
-        end
-
-        it 'returns the value of the block' do
-          result = subject.with_session do |session|
-            "value"
-          end
-
-          expect(result).to eq("value")
-        end
-      end
-    end
-
-    context 'with lease' do
-      before do
-        allow(subject).to receive(:lease_try_obtain).and_return('aldkfjsldfk')
-      end
-
-      it 'tries to obtain a lease' do
-        expect(subject).to receive(:lease_try_obtain)
-        expect(Gitlab::ExclusiveLease).to receive(:cancel)
-
-        # Cannot setup a session, but we should still cancel the lease
-        expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
-      end
-    end
-
-    context 'without lease' do
-      before do
-        allow(subject).to receive(:lease_try_obtain).and_return(nil)
-      end
-
-      it 'returns a NoSessionError error' do
-        expect { subject.with_session }.to raise_error(Mattermost::NoSessionError)
-      end
-    end
-  end
-end
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
deleted file mode 100644
index 2d14be6bcc2d277b017f9ff8ec48923633e33a71..0000000000000000000000000000000000000000
--- a/spec/lib/mattermost/team_spec.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-require 'spec_helper'
-
-describe Mattermost::Team do
-  before do
-    Mattermost::Session.base_uri('http://mattermost.example.com')
-
-    allow_any_instance_of(Mattermost::Client).to receive(:with_session).
-      and_yield(Mattermost::Session.new(nil))
-  end
-
-  describe '#all' do
-    subject { described_class.new(nil).all }
-
-    context 'for valid request' do
-      let(:response) do
-        [{
-           "id" => "xiyro8huptfhdndadpz8r3wnbo",
-           "create_at" => 1482174222155,
-           "update_at" => 1482174222155,
-           "delete_at" => 0,
-           "display_name" => "chatops",
-           "name" => "chatops",
-           "email" => "admin@example.com",
-           "type" => "O",
-           "company_name" => "",
-           "allowed_domains" => "",
-           "invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
-           "allow_open_invite" => false }]
-      end
-
-      before do
-        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-          to_return(
-            status: 200,
-            headers: { 'Content-Type' => 'application/json' },
-            body: response.to_json
-          )
-      end
-
-      it 'returns a token' do
-        is_expected.to eq(response)
-      end
-    end
-
-    context 'for error message' do
-      before do
-        stub_request(:get, 'http://mattermost.example.com/api/v3/teams/all').
-          to_return(
-            status: 500,
-            headers: { 'Content-Type' => 'application/json' },
-            body: {
-              id: 'api.team.list.app_error',
-              message: 'Cannot list teams.',
-              detailed_error: '',
-              request_id: 'obc374man7bx5r3dbc1q5qhf3r',
-              status_code: 500
-            }.to_json
-          )
-      end
-
-      it 'raises an error with message' do
-        expect { subject }.to raise_error(Mattermost::Error, 'Cannot list teams.')
-      end
-    end
-  end
-end