Merge branch 'move_chatops_to_core' into 'master'
Move ChatOps to Core See merge request gitlab-org/gitlab-ce!24780
This commit is contained in:
commit
4f3147b0fd
39 changed files with 1277 additions and 3 deletions
|
@ -47,6 +47,8 @@ module Ci
|
|||
has_many :auto_canceled_pipelines, class_name: 'Ci::Pipeline', foreign_key: 'auto_canceled_by_id'
|
||||
has_many :auto_canceled_jobs, class_name: 'CommitStatus', foreign_key: 'auto_canceled_by_id'
|
||||
|
||||
has_one :chat_data, class_name: 'Ci::PipelineChatData'
|
||||
|
||||
accepts_nested_attributes_for :variables, reject_if: :persisted?
|
||||
|
||||
delegate :id, to: :project, prefix: true
|
||||
|
|
13
app/models/ci/pipeline_chat_data.rb
Normal file
13
app/models/ci/pipeline_chat_data.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
class PipelineChatData < ActiveRecord::Base
|
||||
self.table_name = 'ci_pipeline_chat_data'
|
||||
|
||||
belongs_to :chat_name
|
||||
|
||||
validates :pipeline_id, presence: true
|
||||
validates :chat_name_id, presence: true
|
||||
validates :response_url, presence: true
|
||||
end
|
||||
end
|
|
@ -22,6 +22,7 @@ module Ci
|
|||
schedule: 4,
|
||||
api: 5,
|
||||
external: 6,
|
||||
chat: 8,
|
||||
merge_request: 10
|
||||
}
|
||||
end
|
||||
|
|
|
@ -22,6 +22,10 @@ class SlackSlashCommandsService < SlashCommandsService
|
|||
end
|
||||
end
|
||||
|
||||
def chat_responder
|
||||
::Gitlab::Chat::Responder::Slack
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def format(text)
|
||||
|
|
|
@ -36,6 +36,7 @@ module Ci
|
|||
project: project,
|
||||
current_user: current_user,
|
||||
push_options: params[:push_options],
|
||||
chat_data: params[:chat_data],
|
||||
**extra_options(options))
|
||||
|
||||
sequence = Gitlab::Ci::Pipeline::Chain::Sequence
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
|
||||
- authorized_projects
|
||||
- background_migration
|
||||
- chat_notification
|
||||
- create_gpg_signature
|
||||
- delete_merged_branches
|
||||
- delete_user
|
||||
|
|
|
@ -30,5 +30,6 @@ class BuildFinishedWorker
|
|||
# We execute these async as these are independent operations.
|
||||
BuildHooksWorker.perform_async(build.id)
|
||||
ArchiveTraceWorker.perform_async(build.id)
|
||||
ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat?
|
||||
end
|
||||
end
|
||||
|
|
33
app/workers/chat_notification_worker.rb
Normal file
33
app/workers/chat_notification_worker.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChatNotificationWorker
|
||||
include ApplicationWorker
|
||||
|
||||
RESCHEDULE_INTERVAL = 2.seconds
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def perform(build_id)
|
||||
Ci::Build.find_by(id: build_id).try do |build|
|
||||
send_response(build)
|
||||
end
|
||||
rescue Gitlab::Chat::Output::MissingBuildSectionError
|
||||
# The creation of traces and sections appears to be eventually consistent.
|
||||
# As a result it's possible for us to run the above code before the trace
|
||||
# sections are present. To better handle such cases we'll just reschedule
|
||||
# the job instead of producing an error.
|
||||
self.class.perform_in(RESCHEDULE_INTERVAL, build_id)
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def send_response(build)
|
||||
Gitlab::Chat::Responder.responder_for(build).try do |responder|
|
||||
if build.success?
|
||||
output = Gitlab::Chat::Output.new(build)
|
||||
|
||||
responder.success(output.to_s)
|
||||
else
|
||||
responder.failure
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
5
changelogs/unreleased/move_chatops_to_core.yml
Normal file
5
changelogs/unreleased/move_chatops_to_core.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move ChatOps to Core
|
||||
merge_request: 24780
|
||||
author:
|
||||
type: changed
|
|
@ -86,3 +86,4 @@
|
|||
- [delete_stored_files, 1]
|
||||
- [remote_mirror_notification, 2]
|
||||
- [import_issues_csv, 2]
|
||||
- [chat_notification, 2]
|
||||
|
|
10
lib/gitlab/chat.rb
Normal file
10
lib/gitlab/chat.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
# Returns `true` if Chatops is available for the current instance.
|
||||
def self.available?
|
||||
::Feature.enabled?(:chatops, default_enabled: true)
|
||||
end
|
||||
end
|
||||
end
|
94
lib/gitlab/chat/command.rb
Normal file
94
lib/gitlab/chat/command.rb
Normal file
|
@ -0,0 +1,94 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
# Class for scheduling chat pipelines.
|
||||
#
|
||||
# A Command takes care of creating a `Ci::Pipeline` with all the data
|
||||
# necessary to execute a chat command. This includes data such as the chat
|
||||
# data (e.g. the response URL) and any environment variables that should be
|
||||
# exposed to the chat command.
|
||||
class Command
|
||||
include Utils::StrongMemoize
|
||||
|
||||
attr_reader :project, :chat_name, :name, :arguments, :response_url,
|
||||
:channel
|
||||
|
||||
# project - The Project to schedule the command for.
|
||||
# chat_name - The ChatName belonging to the user that scheduled the
|
||||
# command.
|
||||
# name - The name of the chat command to run.
|
||||
# arguments - The arguments (as a String) to pass to the command.
|
||||
# channel - The channel the message was sent from.
|
||||
# response_url - The URL to send the response back to.
|
||||
def initialize(project:, chat_name:, name:, arguments:, channel:, response_url:)
|
||||
@project = project
|
||||
@chat_name = chat_name
|
||||
@name = name
|
||||
@arguments = arguments
|
||||
@channel = channel
|
||||
@response_url = response_url
|
||||
end
|
||||
|
||||
# Tries to create a new pipeline.
|
||||
#
|
||||
# This method will return a pipeline that _may_ be persisted, or `nil` if
|
||||
# the pipeline could not be created.
|
||||
def try_create_pipeline
|
||||
return unless valid?
|
||||
|
||||
create_pipeline
|
||||
end
|
||||
|
||||
def create_pipeline
|
||||
service = ::Ci::CreatePipelineService.new(
|
||||
project,
|
||||
chat_name.user,
|
||||
ref: branch,
|
||||
sha: commit,
|
||||
chat_data: {
|
||||
chat_name_id: chat_name.id,
|
||||
command: name,
|
||||
arguments: arguments,
|
||||
response_url: response_url
|
||||
}
|
||||
)
|
||||
|
||||
service.execute(:chat) do |pipeline|
|
||||
build_environment_variables(pipeline)
|
||||
build_chat_data(pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
# pipeline - The `Ci::Pipeline` to create the environment variables for.
|
||||
def build_environment_variables(pipeline)
|
||||
pipeline.variables.build(
|
||||
[{ key: 'CHAT_INPUT', value: arguments },
|
||||
{ key: 'CHAT_CHANNEL', value: channel }]
|
||||
)
|
||||
end
|
||||
|
||||
# pipeline - The `Ci::Pipeline` to create the chat data for.
|
||||
def build_chat_data(pipeline)
|
||||
pipeline.build_chat_data(
|
||||
chat_name_id: chat_name.id,
|
||||
response_url: response_url
|
||||
)
|
||||
end
|
||||
|
||||
def valid?
|
||||
branch && commit
|
||||
end
|
||||
|
||||
def branch
|
||||
strong_memoize(:branch) { project.default_branch }
|
||||
end
|
||||
|
||||
def commit
|
||||
strong_memoize(:commit) do
|
||||
project.commit(branch)&.id if branch
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
93
lib/gitlab/chat/output.rb
Normal file
93
lib/gitlab/chat/output.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
# Class for gathering and formatting the output of a `Ci::Build`.
|
||||
class Output
|
||||
attr_reader :build
|
||||
|
||||
MissingBuildSectionError = Class.new(StandardError)
|
||||
|
||||
# The primary trace section to look for.
|
||||
PRIMARY_SECTION = 'chat_reply'
|
||||
|
||||
# The backup trace section in case the primary one could not be found.
|
||||
FALLBACK_SECTION = 'build_script'
|
||||
|
||||
# build - The `Ci::Build` to obtain the output from.
|
||||
def initialize(build)
|
||||
@build = build
|
||||
end
|
||||
|
||||
# Returns a `String` containing the output of the build.
|
||||
#
|
||||
# The output _does not_ include the command that was executed.
|
||||
def to_s
|
||||
offset, length = read_offset_and_length
|
||||
|
||||
trace.read do |stream|
|
||||
stream.seek(offset)
|
||||
|
||||
output = stream
|
||||
.stream
|
||||
.read(length)
|
||||
.force_encoding(Encoding.default_external)
|
||||
|
||||
without_executed_command_line(output)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the offset to seek to and the number of bytes to read relative
|
||||
# to the offset.
|
||||
def read_offset_and_length
|
||||
section = find_build_trace_section(PRIMARY_SECTION) ||
|
||||
find_build_trace_section(FALLBACK_SECTION)
|
||||
|
||||
unless section
|
||||
raise(
|
||||
MissingBuildSectionError,
|
||||
"The build_script trace section could not be found for build #{build.id}"
|
||||
)
|
||||
end
|
||||
|
||||
length = section[:byte_end] - section[:byte_start]
|
||||
|
||||
[section[:byte_start], length]
|
||||
end
|
||||
|
||||
# Removes the line containing the executed command from the build output.
|
||||
#
|
||||
# output - A `String` containing the output of a trace section.
|
||||
def without_executed_command_line(output)
|
||||
# If `output.split("\n")` produces an empty Array then the slicing that
|
||||
# follows it will produce a nil. For example:
|
||||
#
|
||||
# "\n".split("\n") # => []
|
||||
# "\n".split("\n")[1..-1] # => nil
|
||||
#
|
||||
# To work around this we only "join" if we're given an Array.
|
||||
if (converted = output.split("\n")[1..-1])
|
||||
converted.join("\n")
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the trace section for the given name, or `nil` if the section
|
||||
# could not be found.
|
||||
#
|
||||
# name - The name of the trace section to find.
|
||||
def find_build_trace_section(name)
|
||||
trace_sections.find { |s| s[:name] == name }
|
||||
end
|
||||
|
||||
def trace_sections
|
||||
@trace_sections ||= trace.extract_sections
|
||||
end
|
||||
|
||||
def trace
|
||||
@trace ||= build.trace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
22
lib/gitlab/chat/responder.rb
Normal file
22
lib/gitlab/chat/responder.rb
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
module Responder
|
||||
# Returns an instance of the responder to use for generating chat
|
||||
# responses.
|
||||
#
|
||||
# This method will return `nil` if no formatter is available for the given
|
||||
# build.
|
||||
#
|
||||
# build - A `Ci::Build` that executed a chat command.
|
||||
def self.responder_for(build)
|
||||
service = build.pipeline.chat_data&.chat_name&.service
|
||||
|
||||
if (responder = service.try(:chat_responder))
|
||||
responder.new(build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
40
lib/gitlab/chat/responder/base.rb
Normal file
40
lib/gitlab/chat/responder/base.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
module Responder
|
||||
class Base
|
||||
attr_reader :build
|
||||
|
||||
# build - The `Ci::Build` that was executed.
|
||||
def initialize(build)
|
||||
@build = build
|
||||
end
|
||||
|
||||
def pipeline
|
||||
build.pipeline
|
||||
end
|
||||
|
||||
def project
|
||||
pipeline.project
|
||||
end
|
||||
|
||||
def success(*)
|
||||
raise NotImplementedError, 'You must implement #success(output)'
|
||||
end
|
||||
|
||||
def failure
|
||||
raise NotImplementedError, 'You must implement #failure'
|
||||
end
|
||||
|
||||
def send_response(output)
|
||||
raise NotImplementedError, 'You must implement #send_response(output)'
|
||||
end
|
||||
|
||||
def scheduled_output
|
||||
raise NotImplementedError, 'You must implement #scheduled_output'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
80
lib/gitlab/chat/responder/slack.rb
Normal file
80
lib/gitlab/chat/responder/slack.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Chat
|
||||
module Responder
|
||||
class Slack < Responder::Base
|
||||
SUCCESS_COLOR = '#B3ED8E'
|
||||
FAILURE_COLOR = '#FF5640'
|
||||
RESPONSE_TYPE = :in_channel
|
||||
|
||||
# Slack breaks messages apart if they're around 4 KB in size. We use a
|
||||
# slightly smaller limit here to account for user mentions.
|
||||
MESSAGE_SIZE_LIMIT = 3.5.kilobytes
|
||||
|
||||
# Sends a response back to Slack
|
||||
#
|
||||
# output - The output to send back to Slack, as a Hash.
|
||||
def send_response(output)
|
||||
Gitlab::HTTP.post(
|
||||
pipeline.chat_data.response_url,
|
||||
{
|
||||
headers: { Accept: 'application/json' },
|
||||
body: output.to_json
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
# Sends the output for a build that completed successfully.
|
||||
#
|
||||
# output - The output produced by the chat command.
|
||||
def success(output)
|
||||
return if output.empty?
|
||||
|
||||
send_response(
|
||||
text: message_text(limit_output(output)),
|
||||
response_type: RESPONSE_TYPE
|
||||
)
|
||||
end
|
||||
|
||||
# Sends the output for a build that failed.
|
||||
def failure
|
||||
send_response(
|
||||
text: message_text("<#{build_url}|Sorry, the build failed!>"),
|
||||
response_type: RESPONSE_TYPE
|
||||
)
|
||||
end
|
||||
|
||||
# Returns the output to send back after a command has been scheduled.
|
||||
def scheduled_output
|
||||
# We return an empty message so that Slack still shows the input
|
||||
# command, without polluting the channel with standard "The job has
|
||||
# been scheduled" (or similar) responses.
|
||||
{ text: '' }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def limit_output(output)
|
||||
if output.bytesize <= MESSAGE_SIZE_LIMIT
|
||||
output
|
||||
else
|
||||
"<#{build_url}|The output is too large to be sent back directly!>"
|
||||
end
|
||||
end
|
||||
|
||||
def mention_user
|
||||
"<@#{pipeline.chat_data.chat_name.chat_id}>"
|
||||
end
|
||||
|
||||
def message_text(output)
|
||||
"#{mention_user}: #{output}"
|
||||
end
|
||||
|
||||
def build_url
|
||||
::Gitlab::Routing.url_helpers.project_build_url(project, build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,7 +10,8 @@ module Gitlab
|
|||
:origin_ref, :checkout_sha, :after_sha, :before_sha,
|
||||
:trigger_request, :schedule, :merge_request,
|
||||
:ignore_skip_ci, :save_incompleted,
|
||||
:seeds_block, :variables_attributes, :push_options
|
||||
:seeds_block, :variables_attributes, :push_options,
|
||||
:chat_data
|
||||
) do
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
|
|
|
@ -6,7 +6,13 @@ module Gitlab
|
|||
module Chain
|
||||
class RemoveUnwantedChatJobs < Chain::Base
|
||||
def perform!
|
||||
# to be overriden in EE
|
||||
return unless pipeline.config_processor && pipeline.chat?
|
||||
|
||||
# When scheduling a chat pipeline we only want to run the build
|
||||
# that matches the chat command.
|
||||
pipeline.config_processor.jobs.select! do |name, _|
|
||||
name.to_s == command.chat_data[:command].to_s
|
||||
end
|
||||
end
|
||||
|
||||
def break?
|
||||
|
|
25
lib/gitlab/slash_commands/application_help.rb
Normal file
25
lib/gitlab/slash_commands/application_help.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SlashCommands
|
||||
class ApplicationHelp < BaseCommand
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def execute
|
||||
Gitlab::SlashCommands::Presenters::Help.new(commands).present(trigger, params[:text])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trigger
|
||||
"#{params[:command]} [project name or alias]"
|
||||
end
|
||||
|
||||
def commands
|
||||
Gitlab::SlashCommands::Command.commands
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,7 +9,8 @@ module Gitlab
|
|||
Gitlab::SlashCommands::IssueNew,
|
||||
Gitlab::SlashCommands::IssueSearch,
|
||||
Gitlab::SlashCommands::IssueMove,
|
||||
Gitlab::SlashCommands::Deploy
|
||||
Gitlab::SlashCommands::Deploy,
|
||||
Gitlab::SlashCommands::Run
|
||||
]
|
||||
end
|
||||
|
||||
|
|
17
lib/gitlab/slash_commands/presenters/error.rb
Normal file
17
lib/gitlab/slash_commands/presenters/error.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SlashCommands
|
||||
module Presenters
|
||||
class Error < Presenters::Base
|
||||
def initialize(message)
|
||||
@message = message
|
||||
end
|
||||
|
||||
def message
|
||||
ephemeral_response(text: @message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
lib/gitlab/slash_commands/presenters/run.rb
Normal file
33
lib/gitlab/slash_commands/presenters/run.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SlashCommands
|
||||
module Presenters
|
||||
class Run < Presenters::Base
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def present(pipeline)
|
||||
build = pipeline.builds.take
|
||||
|
||||
if build && (responder = Chat::Responder.responder_for(build))
|
||||
in_channel_response(responder.scheduled_output)
|
||||
else
|
||||
unsupported_chat_service
|
||||
end
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def unsupported_chat_service
|
||||
ephemeral_response(text: 'Sorry, this chat service is currently not supported by GitLab ChatOps.')
|
||||
end
|
||||
|
||||
def failed_to_schedule(command)
|
||||
ephemeral_response(
|
||||
text: 'The command could not be scheduled. Make sure that your ' \
|
||||
'project has a .gitlab-ci.yml that defines a job with the ' \
|
||||
"name #{command.inspect}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
44
lib/gitlab/slash_commands/run.rb
Normal file
44
lib/gitlab/slash_commands/run.rb
Normal file
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module SlashCommands
|
||||
# Slash command for triggering chatops jobs.
|
||||
class Run < BaseCommand
|
||||
def self.match(text)
|
||||
/\Arun\s+(?<command>\S+)(\s+(?<arguments>.+))?\z/.match(text)
|
||||
end
|
||||
|
||||
def self.help_message
|
||||
'run <command> <arguments>'
|
||||
end
|
||||
|
||||
def self.available?(project)
|
||||
Chat.available? && project.builds_enabled?
|
||||
end
|
||||
|
||||
def self.allowed?(project, user)
|
||||
can?(user, :create_pipeline, project)
|
||||
end
|
||||
|
||||
def execute(match)
|
||||
command = Chat::Command.new(
|
||||
project: project,
|
||||
chat_name: chat_name,
|
||||
name: match[:command],
|
||||
arguments: match[:arguments],
|
||||
channel: params[:channel_id],
|
||||
response_url: params[:response_url]
|
||||
)
|
||||
|
||||
presenter = Gitlab::SlashCommands::Presenters::Run.new
|
||||
pipeline = command.try_create_pipeline
|
||||
|
||||
if pipeline&.persisted?
|
||||
presenter.present(pipeline)
|
||||
else
|
||||
presenter.failed_to_schedule(command.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
77
spec/lib/gitlab/chat/command_spec.rb
Normal file
77
spec/lib/gitlab/chat/command_spec.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat::Command do
|
||||
let(:chat_name) { create(:chat_name) }
|
||||
|
||||
let(:command) do
|
||||
described_class.new(
|
||||
project: project,
|
||||
chat_name: chat_name,
|
||||
name: 'spinach',
|
||||
arguments: 'foo',
|
||||
channel: '123',
|
||||
response_url: 'http://example.com'
|
||||
)
|
||||
end
|
||||
|
||||
describe '#try_create_pipeline' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
it 'returns nil when the command is not valid' do
|
||||
expect(command)
|
||||
.to receive(:valid?)
|
||||
.and_return(false)
|
||||
|
||||
expect(command.try_create_pipeline).to be_nil
|
||||
end
|
||||
|
||||
it 'tries to create the pipeline when a command is valid' do
|
||||
expect(command)
|
||||
.to receive(:valid?)
|
||||
.and_return(true)
|
||||
|
||||
expect(command)
|
||||
.to receive(:create_pipeline)
|
||||
|
||||
command.try_create_pipeline
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_pipeline' do
|
||||
let(:project) { create(:project, :test_repo) }
|
||||
let(:pipeline) { command.create_pipeline }
|
||||
|
||||
before do
|
||||
stub_repository_ci_yaml_file(sha: project.commit.id)
|
||||
|
||||
project.add_developer(chat_name.user)
|
||||
end
|
||||
|
||||
it 'creates the pipeline' do
|
||||
expect(pipeline).to be_persisted
|
||||
end
|
||||
|
||||
it 'creates the chat data for the pipeline' do
|
||||
expect(pipeline.chat_data).to be_an_instance_of(Ci::PipelineChatData)
|
||||
end
|
||||
|
||||
it 'stores the chat name ID in the chat data' do
|
||||
expect(pipeline.chat_data.chat_name_id).to eq(chat_name.id)
|
||||
end
|
||||
|
||||
it 'stores the response URL in the chat data' do
|
||||
expect(pipeline.chat_data.response_url).to eq('http://example.com')
|
||||
end
|
||||
|
||||
it 'creates the environment variables for the pipeline' do
|
||||
vars = pipeline.variables.each_with_object({}) do |row, hash|
|
||||
hash[row.key] = row.value
|
||||
end
|
||||
|
||||
expect(vars['CHAT_INPUT']).to eq('foo')
|
||||
expect(vars['CHAT_CHANNEL']).to eq('123')
|
||||
end
|
||||
end
|
||||
end
|
101
spec/lib/gitlab/chat/output_spec.rb
Normal file
101
spec/lib/gitlab/chat/output_spec.rb
Normal file
|
@ -0,0 +1,101 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat::Output do
|
||||
let(:build) do
|
||||
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
|
||||
end
|
||||
|
||||
let(:output) { described_class.new(build) }
|
||||
|
||||
describe '#to_s' do
|
||||
it 'returns the build output as a String' do
|
||||
trace = Gitlab::Ci::Trace.new(build)
|
||||
|
||||
trace.set("echo hello\nhello")
|
||||
|
||||
allow(build)
|
||||
.to receive(:trace)
|
||||
.and_return(trace)
|
||||
|
||||
allow(output)
|
||||
.to receive(:read_offset_and_length)
|
||||
.and_return([0, 13])
|
||||
|
||||
expect(output.to_s).to eq('he')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#read_offset_and_length' do
|
||||
context 'without the chat_reply trace section' do
|
||||
it 'falls back to using the build_script trace section' do
|
||||
expect(output)
|
||||
.to receive(:find_build_trace_section)
|
||||
.with('chat_reply')
|
||||
.and_return(nil)
|
||||
|
||||
expect(output)
|
||||
.to receive(:find_build_trace_section)
|
||||
.with('build_script')
|
||||
.and_return({ name: 'build_script', byte_start: 1, byte_end: 4 })
|
||||
|
||||
expect(output.read_offset_and_length).to eq([1, 3])
|
||||
end
|
||||
end
|
||||
|
||||
context 'without the build_script trace section' do
|
||||
it 'raises MissingBuildSectionError' do
|
||||
expect { output.read_offset_and_length }
|
||||
.to raise_error(described_class::MissingBuildSectionError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the chat_reply trace section' do
|
||||
it 'returns the read offset and length as an Array' do
|
||||
trace = Gitlab::Ci::Trace.new(build)
|
||||
|
||||
allow(build)
|
||||
.to receive(:trace)
|
||||
.and_return(trace)
|
||||
|
||||
allow(trace)
|
||||
.to receive(:extract_sections)
|
||||
.and_return([{ name: 'chat_reply', byte_start: 1, byte_end: 4 }])
|
||||
|
||||
expect(output.read_offset_and_length).to eq([1, 3])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#without_executed_command_line' do
|
||||
it 'returns the input without the first line' do
|
||||
expect(output.without_executed_command_line("hello\nworld"))
|
||||
.to eq('world')
|
||||
end
|
||||
|
||||
it 'returns an empty String when the input is empty' do
|
||||
expect(output.without_executed_command_line('')).to eq('')
|
||||
end
|
||||
|
||||
it 'returns an empty String when the input consits of a single newline' do
|
||||
expect(output.without_executed_command_line("\n")).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#find_build_trace_section' do
|
||||
it 'returns nil when no section could be found' do
|
||||
expect(output.find_build_trace_section('foo')).to be_nil
|
||||
end
|
||||
|
||||
it 'returns the trace section when it could be found' do
|
||||
section = { name: 'chat_reply', byte_start: 1, byte_end: 4 }
|
||||
|
||||
allow(output)
|
||||
.to receive(:trace_sections)
|
||||
.and_return([section])
|
||||
|
||||
expect(output.find_build_trace_section('chat_reply')).to eq(section)
|
||||
end
|
||||
end
|
||||
end
|
48
spec/lib/gitlab/chat/responder/base_spec.rb
Normal file
48
spec/lib/gitlab/chat/responder/base_spec.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat::Responder::Base do
|
||||
let(:project) { double(:project) }
|
||||
let(:pipeline) { double(:pipeline, project: project) }
|
||||
let(:build) { double(:build, pipeline: pipeline) }
|
||||
let(:responder) { described_class.new(build) }
|
||||
|
||||
describe '#pipeline' do
|
||||
it 'returns the pipeline' do
|
||||
expect(responder.pipeline).to eq(pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#project' do
|
||||
it 'returns the project' do
|
||||
expect(responder.project).to eq(project)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#success' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { responder.success }.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#failure' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { responder.failure }.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_response' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { responder.send_response('hello') }
|
||||
.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#scheduled_output' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect { responder.scheduled_output }
|
||||
.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
end
|
77
spec/lib/gitlab/chat/responder/slack_spec.rb
Normal file
77
spec/lib/gitlab/chat/responder/slack_spec.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat::Responder::Slack do
|
||||
let(:chat_name) { create(:chat_name, chat_id: 'U123') }
|
||||
|
||||
let(:pipeline) do
|
||||
pipeline = create(:ci_pipeline)
|
||||
|
||||
pipeline.create_chat_data!(
|
||||
response_url: 'http://example.com',
|
||||
chat_name_id: chat_name.id
|
||||
)
|
||||
|
||||
pipeline
|
||||
end
|
||||
|
||||
let(:build) { create(:ci_build, pipeline: pipeline) }
|
||||
let(:responder) { described_class.new(build) }
|
||||
|
||||
describe '#send_response' do
|
||||
it 'sends a response back to Slack' do
|
||||
expect(Gitlab::HTTP).to receive(:post).with(
|
||||
'http://example.com',
|
||||
{ headers: { Accept: 'application/json' }, body: 'hello'.to_json }
|
||||
)
|
||||
|
||||
responder.send_response('hello')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#success' do
|
||||
it 'returns the output for a successful build' do
|
||||
expect(responder)
|
||||
.to receive(:send_response)
|
||||
.with(hash_including(text: /<@U123>:.+hello/, response_type: :in_channel))
|
||||
|
||||
responder.success('hello')
|
||||
end
|
||||
|
||||
it 'limits the output to a fixed size' do
|
||||
expect(responder)
|
||||
.to receive(:send_response)
|
||||
.with(hash_including(text: /The output is too large/))
|
||||
|
||||
responder.success('a' * 4000)
|
||||
end
|
||||
|
||||
it 'does not send a response if the output is empty' do
|
||||
expect(responder).not_to receive(:send_response)
|
||||
|
||||
responder.success('')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#failure' do
|
||||
it 'returns the output for a failed build' do
|
||||
expect(responder).to receive(:send_response).with(
|
||||
hash_including(
|
||||
text: /<@U123>:.+Sorry, the build failed!/,
|
||||
response_type: :in_channel
|
||||
)
|
||||
)
|
||||
|
||||
responder.failure
|
||||
end
|
||||
end
|
||||
|
||||
describe '#scheduled_output' do
|
||||
it 'returns the output for a scheduled build' do
|
||||
output = responder.scheduled_output
|
||||
|
||||
expect(output).to eq({ text: '' })
|
||||
end
|
||||
end
|
||||
end
|
32
spec/lib/gitlab/chat/responder_spec.rb
Normal file
32
spec/lib/gitlab/chat/responder_spec.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat::Responder do
|
||||
describe '.responder_for' do
|
||||
context 'using a regular build' do
|
||||
it 'returns nil' do
|
||||
build = create(:ci_build)
|
||||
|
||||
expect(described_class.responder_for(build)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'using a chat build' do
|
||||
it 'returns the responder for the build' do
|
||||
pipeline = create(:ci_pipeline)
|
||||
build = create(:ci_build, pipeline: pipeline)
|
||||
service = double(:service, chat_responder: Gitlab::Chat::Responder::Slack)
|
||||
chat_name = double(:chat_name, service: service)
|
||||
chat_data = double(:chat_data, chat_name: chat_name)
|
||||
|
||||
allow(pipeline)
|
||||
.to receive(:chat_data)
|
||||
.and_return(chat_data)
|
||||
|
||||
expect(described_class.responder_for(build))
|
||||
.to be_an_instance_of(Gitlab::Chat::Responder::Slack)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
spec/lib/gitlab/chat_spec.rb
Normal file
23
spec/lib/gitlab/chat_spec.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
|
||||
describe '.available?' do
|
||||
it 'returns true when the chatops feature is available' do
|
||||
allow(Feature)
|
||||
.to receive(:enabled?)
|
||||
.with(:chatops, default_enabled: true)
|
||||
.and_return(true)
|
||||
|
||||
expect(described_class).to be_available
|
||||
end
|
||||
|
||||
it 'returns false when the chatops feature is not available' do
|
||||
allow(Feature)
|
||||
.to receive(:enabled?)
|
||||
.with(:chatops, default_enabled: true)
|
||||
.and_return(false)
|
||||
|
||||
expect(described_class).not_to be_available
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline_with_one_job, project: project, ref: 'master')
|
||||
end
|
||||
|
||||
let(:command) do
|
||||
double(:command, project: project, chat_data: { command: 'echo' })
|
||||
end
|
||||
|
||||
describe '#perform!' do
|
||||
it 'removes unwanted jobs for chat pipelines' do
|
||||
allow(pipeline).to receive(:chat?).and_return(true)
|
||||
|
||||
pipeline.config_processor.jobs[:echo] = double(:job)
|
||||
|
||||
described_class.new(pipeline, command).perform!
|
||||
|
||||
expect(pipeline.config_processor.jobs.keys).to eq([:echo])
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not remove any jobs for non-chat pipelines' do
|
||||
described_class.new(pipeline, command).perform!
|
||||
|
||||
expect(pipeline.config_processor.jobs.keys).to eq([:rspec])
|
||||
end
|
||||
end
|
|
@ -131,6 +131,7 @@ ci_pipelines:
|
|||
- merge_request
|
||||
- deployments
|
||||
- environments
|
||||
- chat_data
|
||||
pipeline_variables:
|
||||
- pipeline
|
||||
stages:
|
||||
|
|
19
spec/lib/gitlab/slash_commands/application_help_spec.rb
Normal file
19
spec/lib/gitlab/slash_commands/application_help_spec.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SlashCommands::ApplicationHelp do
|
||||
let(:params) { { command: '/gitlab', text: 'help' } }
|
||||
|
||||
describe '#execute' do
|
||||
subject do
|
||||
described_class.new(params).execute
|
||||
end
|
||||
|
||||
it 'displays the help section' do
|
||||
expect(subject[:response_type]).to be(:ephemeral)
|
||||
expect(subject[:text]).to include('Available commands')
|
||||
expect(subject[:text]).to include('/gitlab [project name or alias] issue show')
|
||||
end
|
||||
end
|
||||
end
|
15
spec/lib/gitlab/slash_commands/presenters/error_spec.rb
Normal file
15
spec/lib/gitlab/slash_commands/presenters/error_spec.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SlashCommands::Presenters::Error do
|
||||
subject { described_class.new('Error').message }
|
||||
|
||||
it { is_expected.to be_a(Hash) }
|
||||
|
||||
it 'shows the error message' do
|
||||
expect(subject[:response_type]).to be(:ephemeral)
|
||||
expect(subject[:status]).to eq(200)
|
||||
expect(subject[:text]).to eq('Error')
|
||||
end
|
||||
end
|
79
spec/lib/gitlab/slash_commands/presenters/run_spec.rb
Normal file
79
spec/lib/gitlab/slash_commands/presenters/run_spec.rb
Normal file
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SlashCommands::Presenters::Run do
|
||||
let(:presenter) { described_class.new }
|
||||
|
||||
describe '#present' do
|
||||
context 'when no builds are present' do
|
||||
it 'returns an error' do
|
||||
builds = double(:builds, take: nil)
|
||||
pipeline = double(:pipeline, builds: builds)
|
||||
|
||||
expect(presenter)
|
||||
.to receive(:unsupported_chat_service)
|
||||
|
||||
presenter.present(pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a responder could be found' do
|
||||
it 'returns the output for a scheduled pipeline' do
|
||||
responder = double(:responder, scheduled_output: 'hello')
|
||||
build = double(:build)
|
||||
builds = double(:builds, take: build)
|
||||
pipeline = double(:pipeline, builds: builds)
|
||||
|
||||
allow(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(build)
|
||||
.and_return(responder)
|
||||
|
||||
expect(presenter)
|
||||
.to receive(:in_channel_response)
|
||||
.with('hello')
|
||||
|
||||
presenter.present(pipeline)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a responder could not be found' do
|
||||
it 'returns an error' do
|
||||
build = double(:build)
|
||||
builds = double(:builds, take: build)
|
||||
pipeline = double(:pipeline, builds: builds)
|
||||
|
||||
allow(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(build)
|
||||
.and_return(nil)
|
||||
|
||||
expect(presenter)
|
||||
.to receive(:unsupported_chat_service)
|
||||
|
||||
presenter.present(pipeline)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#unsupported_chat_service' do
|
||||
it 'returns an ephemeral response' do
|
||||
expect(presenter)
|
||||
.to receive(:ephemeral_response)
|
||||
.with(text: /Sorry, this chat service is currently not supported/)
|
||||
|
||||
presenter.unsupported_chat_service
|
||||
end
|
||||
end
|
||||
|
||||
describe '#failed_to_schedule' do
|
||||
it 'returns an ephemeral response' do
|
||||
expect(presenter)
|
||||
.to receive(:ephemeral_response)
|
||||
.with(text: /The command could not be scheduled/)
|
||||
|
||||
presenter.failed_to_schedule('foo')
|
||||
end
|
||||
end
|
||||
end
|
123
spec/lib/gitlab/slash_commands/run_spec.rb
Normal file
123
spec/lib/gitlab/slash_commands/run_spec.rb
Normal file
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::SlashCommands::Run do
|
||||
describe '.available?' do
|
||||
it 'returns true when builds are enabled for the project' do
|
||||
project = double(:project, builds_enabled?: true)
|
||||
|
||||
allow(Gitlab::Chat)
|
||||
.to receive(:available?)
|
||||
.and_return(true)
|
||||
|
||||
expect(described_class.available?(project)).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when builds are disabled for the project' do
|
||||
project = double(:project, builds_enabled?: false)
|
||||
|
||||
expect(described_class.available?(project)).to eq(false)
|
||||
end
|
||||
|
||||
it 'returns false when chatops is not available' do
|
||||
allow(Gitlab::Chat)
|
||||
.to receive(:available?)
|
||||
.and_return(false)
|
||||
|
||||
project = double(:project, builds_enabled?: true)
|
||||
|
||||
expect(described_class.available?(project)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.allowed?' do
|
||||
it 'returns true when the user can create a pipeline' do
|
||||
project = create(:project)
|
||||
|
||||
expect(described_class.allowed?(project, project.creator)).to eq(true)
|
||||
end
|
||||
|
||||
it 'returns false when the user can not create a pipeline' do
|
||||
project = create(:project)
|
||||
user = create(:user)
|
||||
|
||||
expect(described_class.allowed?(project, user)).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:chat_name) { create(:chat_name) }
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:command) do
|
||||
described_class.new(project, chat_name, response_url: 'http://example.com')
|
||||
end
|
||||
|
||||
context 'when a pipeline could not be scheduled' do
|
||||
it 'returns an error' do
|
||||
expect_any_instance_of(Gitlab::Chat::Command)
|
||||
.to receive(:try_create_pipeline)
|
||||
.and_return(nil)
|
||||
|
||||
expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
|
||||
.to receive(:failed_to_schedule)
|
||||
.with('foo')
|
||||
|
||||
command.execute(command: 'foo', arguments: '')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a pipeline could be created but the chat service was not supported' do
|
||||
it 'returns an error' do
|
||||
build = double(:build)
|
||||
pipeline = double(
|
||||
:pipeline,
|
||||
builds: double(:relation, take: build),
|
||||
persisted?: true
|
||||
)
|
||||
|
||||
expect_any_instance_of(Gitlab::Chat::Command)
|
||||
.to receive(:try_create_pipeline)
|
||||
.and_return(pipeline)
|
||||
|
||||
expect(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(build)
|
||||
.and_return(nil)
|
||||
|
||||
expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
|
||||
.to receive(:unsupported_chat_service)
|
||||
|
||||
command.execute(command: 'foo', arguments: '')
|
||||
end
|
||||
end
|
||||
|
||||
context 'using a valid pipeline' do
|
||||
it 'schedules the pipeline' do
|
||||
responder = double(:responder, scheduled_output: 'hello')
|
||||
build = double(:build)
|
||||
pipeline = double(
|
||||
:pipeline,
|
||||
builds: double(:relation, take: build),
|
||||
persisted?: true
|
||||
)
|
||||
|
||||
expect_any_instance_of(Gitlab::Chat::Command)
|
||||
.to receive(:try_create_pipeline)
|
||||
.and_return(pipeline)
|
||||
|
||||
expect(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(build)
|
||||
.and_return(responder)
|
||||
|
||||
expect_any_instance_of(Gitlab::SlashCommands::Presenters::Run)
|
||||
.to receive(:in_channel_response)
|
||||
.with(responder.scheduled_output)
|
||||
|
||||
command.execute(command: 'foo', arguments: '')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,6 +22,7 @@ describe Ci::Pipeline, :mailer do
|
|||
it { is_expected.to have_many(:builds) }
|
||||
it { is_expected.to have_many(:auto_canceled_pipelines) }
|
||||
it { is_expected.to have_many(:auto_canceled_jobs) }
|
||||
it { is_expected.to have_one(:chat_data) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:sha) }
|
||||
it { is_expected.to validate_presence_of(:status) }
|
||||
|
|
|
@ -38,4 +38,11 @@ describe SlackSlashCommandsService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#chat_responder' do
|
||||
it 'returns the responder to use for Slack' do
|
||||
expect(described_class.new.chat_responder)
|
||||
.to eq(Gitlab::Chat::Responder::Slack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,5 +26,24 @@ describe BuildFinishedWorker do
|
|||
.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
it 'schedules a ChatNotification job for a chat build' do
|
||||
build = create(:ci_build, :success, pipeline: create(:ci_pipeline, source: :chat))
|
||||
|
||||
expect(ChatNotificationWorker)
|
||||
.to receive(:perform_async)
|
||||
.with(build.id)
|
||||
|
||||
described_class.new.perform(build.id)
|
||||
end
|
||||
|
||||
it 'does not schedule a ChatNotification job for a regular build' do
|
||||
build = create(:ci_build, :success, pipeline: create(:ci_pipeline))
|
||||
|
||||
expect(ChatNotificationWorker)
|
||||
.not_to receive(:perform_async)
|
||||
|
||||
described_class.new.perform(build.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
91
spec/workers/chat_notification_worker_spec.rb
Normal file
91
spec/workers/chat_notification_worker_spec.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe ChatNotificationWorker do
|
||||
let(:worker) { described_class.new }
|
||||
let(:chat_build) do
|
||||
create(:ci_build, pipeline: create(:ci_pipeline, source: :chat))
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it 'does nothing when the build no longer exists' do
|
||||
expect(worker).not_to receive(:send_response)
|
||||
|
||||
worker.perform(-1)
|
||||
end
|
||||
|
||||
it 'sends a response for an existing build' do
|
||||
expect(worker)
|
||||
.to receive(:send_response)
|
||||
.with(an_instance_of(Ci::Build))
|
||||
|
||||
worker.perform(chat_build.id)
|
||||
end
|
||||
|
||||
it 'reschedules the job if the trace sections could not be found' do
|
||||
expect(worker)
|
||||
.to receive(:send_response)
|
||||
.and_raise(Gitlab::Chat::Output::MissingBuildSectionError)
|
||||
|
||||
expect(described_class)
|
||||
.to receive(:perform_in)
|
||||
.with(described_class::RESCHEDULE_INTERVAL, chat_build.id)
|
||||
|
||||
worker.perform(chat_build.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#send_response' do
|
||||
context 'when a responder could not be found' do
|
||||
it 'does nothing' do
|
||||
expect(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(chat_build)
|
||||
.and_return(nil)
|
||||
|
||||
expect(worker.send_response(chat_build)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a responder could be found' do
|
||||
let(:responder) { double(:responder) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Chat::Responder)
|
||||
.to receive(:responder_for)
|
||||
.with(chat_build)
|
||||
.and_return(responder)
|
||||
end
|
||||
|
||||
it 'sends the response for a succeeded build' do
|
||||
output = double(:output, to_s: 'this is the build output')
|
||||
|
||||
expect(chat_build)
|
||||
.to receive(:success?)
|
||||
.and_return(true)
|
||||
|
||||
expect(responder)
|
||||
.to receive(:success)
|
||||
.with(an_instance_of(String))
|
||||
|
||||
expect(Gitlab::Chat::Output)
|
||||
.to receive(:new)
|
||||
.with(chat_build)
|
||||
.and_return(output)
|
||||
|
||||
worker.send_response(chat_build)
|
||||
end
|
||||
|
||||
it 'sends the response for a failed build' do
|
||||
expect(chat_build)
|
||||
.to receive(:success?)
|
||||
.and_return(false)
|
||||
|
||||
expect(responder).to receive(:failure)
|
||||
|
||||
worker.send_response(chat_build)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue