Maintenance: Bump rubocop from 1.16.0 to 1.18.3.

This commit is contained in:
Thorsten Eckel 2021-07-12 13:18:31 +00:00 committed by Martin Gruner
parent d70d74450b
commit 32a3e31db8
22 changed files with 92 additions and 80 deletions

View file

@ -497,7 +497,7 @@ GEM
rspec-support (~> 3.10) rspec-support (~> 3.10)
rspec-support (3.10.2) rspec-support (3.10.2)
rszr (0.5.2) rszr (0.5.2)
rubocop (1.16.0) rubocop (1.18.3)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.0.0.0) parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)

View file

@ -49,7 +49,7 @@ class App.TicketMerge extends App.ControllerModal
radio: true radio: true
) )
content.delegate('[name="master_ticket_number"]', 'focus', (e) -> content.delegate('[name="target_ticket_number"]', 'focus', (e) ->
$(e.target).parents().find('[name="radio"]').prop('checked', false) $(e.target).parents().find('[name="radio"]').prop('checked', false)
) )
@ -57,7 +57,7 @@ class App.TicketMerge extends App.ControllerModal
if $(e.target).prop('checked') if $(e.target).prop('checked')
ticket_id = $(e.target).val() ticket_id = $(e.target).val()
ticket = App.Ticket.fullLocal(ticket_id) ticket = App.Ticket.fullLocal(ticket_id)
$(e.target).parents().find('[name="master_ticket_number"]').val(ticket.number) $(e.target).parents().find('[name="target_ticket_number"]').val(ticket.number)
) )
content content
@ -66,7 +66,7 @@ class App.TicketMerge extends App.ControllerModal
@formDisable(e) @formDisable(e)
params = @formParam(e.target) params = @formParam(e.target)
if !params.master_ticket_number if !params.target_ticket_number
alert(App.i18n.translateInline('%s required!', 'Ticket#')) alert(App.i18n.translateInline('%s required!', 'Ticket#'))
@formEnable(e) @formEnable(e)
return return
@ -75,30 +75,30 @@ class App.TicketMerge extends App.ControllerModal
@ajax( @ajax(
id: 'ticket_merge' id: 'ticket_merge'
type: 'PUT' type: 'PUT'
url: "#{@apiPath}/ticket_merge/#{@ticket.id}/#{params.master_ticket_number}" url: "#{@apiPath}/ticket_merge/#{@ticket.id}/#{params.target_ticket_number}"
processData: true, processData: true,
success: (data, status, xhr) => success: (data, status, xhr) =>
if data['result'] is 'success' if data['result'] is 'success'
# update collection # update collection
App.Collection.load(type: 'Ticket', data: [data.master_ticket]) App.Collection.load(type: 'Ticket', data: [data.target_ticket])
App.Collection.load(type: 'Ticket', data: [data.slave_ticket]) App.Collection.load(type: 'Ticket', data: [data.source_ticket])
# hide dialog # hide dialog
@close() @close()
# view ticket # view ticket
@log 'notice', 'nav...', App.Ticket.find(data.master_ticket['id']) @log 'notice', 'nav...', App.Ticket.find(data.target_ticket['id'])
@navigate '#ticket/zoom/' + data.master_ticket['id'] @navigate '#ticket/zoom/' + data.target_ticket['id']
# notify UI # notify UI
@notify @notify
type: 'success' type: 'success'
msg: App.i18n.translateContent('Ticket %s merged!', data.slave_ticket['number']) msg: App.i18n.translateContent('Ticket %s merged!', data.source_ticket['number'])
timeout: 4000 timeout: 4000
App.TaskManager.remove("Ticket-#{data.slave_ticket['id']}") App.TaskManager.remove("Ticket-#{data.source_ticket['id']}")
else else

View file

@ -1,5 +1,5 @@
class App.Chat extends App.Model class App.Chat extends App.Model
@configure 'Chat', 'name', 'active', 'public', 'max_queue', 'block_ip', 'whitelisted_websites', 'block_country', 'note' @configure 'Chat', 'name', 'active', 'public', 'max_queue', 'block_ip', 'allowed_websites', 'block_country', 'note'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/chats' @url: @apiPath + '/chats'
@countries: @countries:
@ -250,7 +250,7 @@ class App.Chat extends App.Model
{ name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true }, { name: 'note', display: 'Note', tag: 'textarea', limit: 250, null: true },
{ name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 }, { name: 'max_queue', display: 'Max. clients in waitlist', tag: 'input', default: 2 },
{ name: 'block_ip', display: 'Blocked IPs (separated by ;)', tag: 'input', default: '', null: true }, { name: 'block_ip', display: 'Blocked IPs (separated by ;)', tag: 'input', default: '', null: true },
{ name: 'whitelisted_websites', display: 'Allow websites (separated by ;)', tag: 'input', default: '', null: true }, { name: 'allowed_websites', display: 'Allow websites (separated by ;)', tag: 'input', default: '', null: true },
{ name: 'block_country', display: 'Blocked countries', tag: 'column_select', multiple: true, null: true, default: '', options: @countries, seperator: ';' }, { name: 'block_country', display: 'Blocked countries', tag: 'column_select', multiple: true, null: true, default: '', options: @countries, seperator: ';' },
{ name: 'active', display: 'Active', tag: 'active', default: true }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },

View file

@ -1,6 +1,6 @@
<div> <div>
<h4><%- @T( 'Merge to Ticket#' ) %></h4> <h4><%- @T( 'Merge to Ticket#' ) %></h4>
<input type="text" name="master_ticket_number" class="form-control" value=""/> <input type="text" name="target_ticket_number" class="form-control" value=""/>
<hr> <hr>
<h4><%- @T( 'Recent Customer Tickets' ) %></h4> <h4><%- @T( 'Recent Customer Tickets' ) %></h4>
<div id="ticket-merge-customer-tickets"></div> <div id="ticket-merge-customer-tickets"></div>

View file

@ -385,39 +385,39 @@ class TicketsController < ApplicationController
# PUT /api/v1/ticket_merge/1/1 # PUT /api/v1/ticket_merge/1/1
def ticket_merge def ticket_merge
# check master ticket # check target ticket
ticket_master = Ticket.find_by(number: params[:master_ticket_number]) target_ticket = Ticket.find_by(number: params[:target_ticket_number])
if !ticket_master if !target_ticket
render json: { render json: {
result: 'failed', result: 'failed',
message: 'No such master ticket number!', message: 'No such target ticket number!',
} }
return return
end end
authorize!(ticket_master, :update?) authorize!(target_ticket, :update?)
# check slave ticket # check source ticket
ticket_slave = Ticket.find_by(id: params[:slave_ticket_id]) source_ticket = Ticket.find_by(id: params[:source_ticket_id])
if !ticket_slave if !source_ticket
render json: { render json: {
result: 'failed', result: 'failed',
message: 'No such slave ticket!', message: 'No such source ticket!',
} }
return return
end end
authorize!(ticket_slave, :update?) authorize!(source_ticket, :update?)
# merge ticket # merge ticket
ticket_slave.merge_to( source_ticket.merge_to(
ticket_id: ticket_master.id, ticket_id: target_ticket.id,
created_by_id: current_user.id, created_by_id: current_user.id,
) )
# return result # return result
render json: { render json: {
result: 'success', result: 'success',
master_ticket: ticket_master.attributes, target_ticket: target_ticket.attributes,
slave_ticket: ticket_slave.attributes, source_ticket: source_ticket.attributes,
} }
end end

View file

@ -2,7 +2,7 @@
class TriggerWebhookJob < ApplicationJob class TriggerWebhookJob < ApplicationJob
USER_ATTRIBUTE_BLACKLIST = %w[ USER_ATTRIBUTE_FILTER = %w[
last_login last_login
login_failed login_failed
password password

View file

@ -2,7 +2,7 @@
class TriggerWebhookJob::RecordPayload::Base class TriggerWebhookJob::RecordPayload::Base
USER_ATTRIBUTE_BLACKLIST = %w[ USER_ATTRIBUTE_FILTER = %w[
last_login last_login
login_failed login_failed
password password
@ -49,7 +49,7 @@ class TriggerWebhookJob::RecordPayload::Base
attributes = attributes_with_association_names(record) attributes = attributes_with_association_names(record)
return attributes if !record.instance_of?(::User) return attributes if !record.instance_of?(::User)
attributes.except(*USER_ATTRIBUTE_BLACKLIST) attributes.except(*USER_ATTRIBUTE_FILTER)
end end
def attributes_with_association_names(record) def attributes_with_association_names(record)

View file

@ -625,15 +625,15 @@ check if ip address is blocked for chat
check if website is allowed for chat check if website is allowed for chat
chat = Chat.find(123) chat = Chat.find(123)
chat.website_whitelisted?('zammad.org') chat.website_allowed?('zammad.org')
=end =end
def website_whitelisted?(website) def website_allowed?(website)
return true if whitelisted_websites.blank? return true if allowed_websites.blank?
whitelisted_websites.split(';').any? do |whitelisted_website| allowed_websites.split(';').any? do |allowed_website|
website.downcase.include?(whitelisted_website.downcase.strip) website.downcase.include?(allowed_website.downcase.strip)
end end
end end

View file

@ -13,7 +13,7 @@ Rails.application.config.html_sanitizer_tags_quote_content = %w[
] ]
# only this tags are allowed # only this tags are allowed
Rails.application.config.html_sanitizer_tags_whitelist = %w[ Rails.application.config.html_sanitizer_tags_allowlist = %w[
a abbr acronym address area article aside audio a abbr acronym address area article aside audio
b bdi bdo big blockquote br b bdi bdo big blockquote br
canvas caption center cite code col colgroup command canvas caption center cite code col colgroup command
@ -26,7 +26,7 @@ Rails.application.config.html_sanitizer_tags_whitelist = %w[
] ]
# attributes allowed for tags # attributes allowed for tags
Rails.application.config.html_sanitizer_attributes_whitelist = { Rails.application.config.html_sanitizer_attributes_allowlist = {
:all => %w[class dir lang title translate data-signature data-signature-id], :all => %w[class dir lang title translate data-signature data-signature-id],
'a' => %w[href hreflang name rel data-target-id data-target-type data-mention-user-id], 'a' => %w[href hreflang name rel data-target-id data-target-type data-mention-user-id],
'abbr' => %w[title], 'abbr' => %w[title],
@ -52,7 +52,7 @@ Rails.application.config.html_sanitizer_attributes_whitelist = {
} }
# only this css properties are allowed # only this css properties are allowed
Rails.application.config.html_sanitizer_css_properties_whitelist = { Rails.application.config.html_sanitizer_css_properties_allowlist = {
'img' => %w[ 'img' => %w[
width height width height
max-width min-width max-width min-width
@ -110,7 +110,7 @@ Rails.application.config.html_sanitizer_css_properties_whitelist = {
], ],
} }
Rails.application.config.html_sanitizer_css_values_backlist = { Rails.application.config.html_sanitizer_css_values_blocklist = {
'div' => [ 'div' => [
'color:white', 'color:white',
'color:black', 'color:black',

View file

@ -17,7 +17,7 @@ Zammad::Application.routes.draw do
match api_path + '/ticket_customer', to: 'tickets#ticket_customer', via: :get match api_path + '/ticket_customer', to: 'tickets#ticket_customer', via: :get
match api_path + '/ticket_related/:ticket_id', to: 'tickets#ticket_related', via: :get match api_path + '/ticket_related/:ticket_id', to: 'tickets#ticket_related', via: :get
match api_path + '/ticket_recent', to: 'tickets#ticket_recent', via: :get match api_path + '/ticket_recent', to: 'tickets#ticket_recent', via: :get
match api_path + '/ticket_merge/:slave_ticket_id/:master_ticket_number', to: 'tickets#ticket_merge', via: :put match api_path + '/ticket_merge/:source_ticket_id/:target_ticket_number', to: 'tickets#ticket_merge', via: :put
match api_path + '/ticket_stats', to: 'tickets#stats', via: :get match api_path + '/ticket_stats', to: 'tickets#stats', via: :get
# ticket overviews # ticket overviews

View file

@ -479,7 +479,7 @@ class CreateTicket < ActiveRecord::Migration[4.2]
t.boolean :public, null: false, default: false t.boolean :public, null: false, default: false
t.string :block_ip, limit: 5000, null: true t.string :block_ip, limit: 5000, null: true
t.string :block_country, limit: 5000, null: true t.string :block_country, limit: 5000, null: true
t.string :whitelisted_websites, limit: 5000, null: true t.string :allowed_websites, limit: 5000, null: true
t.string :preferences, limit: 5000, null: true t.string :preferences, limit: 5000, null: true
t.integer :updated_by_id, null: false t.integer :updated_by_id, null: false
t.integer :created_by_id, null: false t.integer :created_by_id, null: false

View file

@ -6,6 +6,6 @@ class ChatAddAllowWebsite < ActiveRecord::Migration[5.1]
# return if it's a new setup # return if it's a new setup
return if !Setting.exists?(name: 'system_init_done') return if !Setting.exists?(name: 'system_init_done')
add_column :chats, :whitelisted_websites, :string, limit: 5000, null: true add_column :chats, :whitelisted_websites, :string, limit: 5000, null: true # rubocop:disable Naming/InclusiveLanguage
end end
end end

View file

@ -0,0 +1,12 @@
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
class InclusiveWording < ActiveRecord::Migration[6.0]
def up
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
rename_column :chats, :whitelisted_websites, :allowed_websites # rubocop:disable Naming/InclusiveLanguage
Chat.reset_column_information
end
end

View file

@ -22,14 +22,14 @@ satinize html string based on whiltelist
# config # config
tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content tags_remove_content = Rails.configuration.html_sanitizer_tags_remove_content
tags_quote_content = Rails.configuration.html_sanitizer_tags_quote_content tags_quote_content = Rails.configuration.html_sanitizer_tags_quote_content
tags_whitelist = Rails.configuration.html_sanitizer_tags_whitelist tags_allowlist = Rails.configuration.html_sanitizer_tags_allowlist
attributes_whitelist = Rails.configuration.html_sanitizer_attributes_whitelist attributes_allowlist = Rails.configuration.html_sanitizer_attributes_allowlist
css_properties_whitelist = Rails.configuration.html_sanitizer_css_properties_whitelist css_properties_allowlist = Rails.configuration.html_sanitizer_css_properties_allowlist
css_values_blacklist = Rails.application.config.html_sanitizer_css_values_backlist css_values_blocklist = Rails.application.config.html_sanitizer_css_values_blocklist
# We whitelist yahoo_quoted because Yahoo Mail marks quoted email content using # We allowlist yahoo_quoted because Yahoo Mail marks quoted email content using
# <div class='yahoo_quoted'> and we rely on this class to identify quoted messages # <div class='yahoo_quoted'> and we rely on this class to identify quoted messages
classes_whitelist = %w[js-signatureMarker yahoo_quoted] classes_allowlist = %w[js-signatureMarker yahoo_quoted]
attributes_2_css = %w[width height] attributes_2_css = %w[width height]
# remove tags with subtree # remove tags with subtree
@ -56,7 +56,7 @@ satinize html string based on whiltelist
scrubber_wipe = Loofah::Scrubber.new do |node| scrubber_wipe = Loofah::Scrubber.new do |node|
# replace tags, keep subtree # replace tags, keep subtree
if tags_whitelist.exclude?(node.name) if tags_allowlist.exclude?(node.name)
node.replace node.children.to_s node.replace node.children.to_s
Loofah::Scrubber::STOP Loofah::Scrubber::STOP
end end
@ -75,7 +75,7 @@ satinize html string based on whiltelist
classes = node['class'].gsub(%r{\t|\n|\r}, '').split classes = node['class'].gsub(%r{\t|\n|\r}, '').split
class_new = '' class_new = ''
classes.each do |local_class| classes.each do |local_class|
next if classes_whitelist.exclude?(local_class.to_s.strip) next if classes_allowlist.exclude?(local_class.to_s.strip)
if class_new != '' if class_new != ''
class_new += ' ' class_new += ' '
@ -115,9 +115,9 @@ satinize html string based on whiltelist
next if !prop[0] next if !prop[0]
key = prop[0].strip key = prop[0].strip
next if css_properties_whitelist.exclude?(node.name) next if css_properties_allowlist.exclude?(node.name)
next if css_properties_whitelist[node.name].exclude?(key) next if css_properties_allowlist[node.name].exclude?(key)
next if css_values_blacklist[node.name]&.include?(local_pear.gsub(%r{[[:space:]]}, '').strip) next if css_values_blocklist[node.name]&.include?(local_pear.gsub(%r{[[:space:]]}, '').strip)
style += "#{local_pear};" style += "#{local_pear};"
end end
@ -137,10 +137,10 @@ satinize html string based on whiltelist
node.delete(attribute_name) node.delete(attribute_name)
end end
# remove attributes if not whitelisted # remove attributes if not allowlisted
node.each do |attribute, _value| node.each do |attribute, _value|
attribute_name = attribute.downcase attribute_name = attribute.downcase
next if attributes_whitelist[:all].include?(attribute_name) || attributes_whitelist[node.name]&.include?(attribute_name) next if attributes_allowlist[:all].include?(attribute_name) || attributes_allowlist[node.name]&.include?(attribute_name)
node.delete(attribute) node.delete(attribute)
end end

View file

@ -6,7 +6,7 @@ class Ldap
class User class User
include Ldap::FilterLookup include Ldap::FilterLookup
BLACKLISTED = %i[ IGNORED_ATTRIBUTES = %i[
admincount admincount
accountexpires accountexpires
badpasswordtime badpasswordtime
@ -124,7 +124,7 @@ class Ldap
pre_merge_count = attributes.count pre_merge_count = attributes.count
attributes.reverse_merge!(entry.to_h attributes.reverse_merge!(entry.to_h
.except(*BLACKLISTED) .except(*IGNORED_ATTRIBUTES)
.transform_values(&:first) .transform_values(&:first)
.compact) .compact)

View file

@ -72,7 +72,7 @@ return is sent as message back to peer
end end
def blocked_origin? def blocked_origin?
return false if current_chat.website_whitelisted?(origin) return false if current_chat.website_allowed?(origin)
send_unavailable send_unavailable
true true

View file

@ -8,14 +8,14 @@ RSpec.describe Issue2019FixDoubleDomainLinksInTriggerEmails, type: :db_migration
# rubocop:disable Lint/InterpolationCheck # rubocop:disable Lint/InterpolationCheck
let(:faulty_link) do let(:faulty_link) do
'<a href="https://example.com/#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">' \ '<a href="https://example.com/#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">' \
'View ticket' \ 'View ticket' \
'</a>' '</a>'
end end
let(:fixed_link) do let(:fixed_link) do
'<a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">' \ '<a href="#{config.http_type}://#{config.fqdn}/#ticket/zoom/#{ticket.id}">' \
'View ticket' \ 'View ticket' \
'</a>' '</a>'
end end
# rubocop:enable Lint/InterpolationCheck # rubocop:enable Lint/InterpolationCheck

View file

@ -2,12 +2,12 @@
RSpec.shared_examples 'TriggerWebhookJob::RecordPayload backend' do |factory| RSpec.shared_examples 'TriggerWebhookJob::RecordPayload backend' do |factory|
describe 'const USER_ATTRIBUTE_BLACKLIST' do describe 'const USER_ATTRIBUTE_FILTER' do
subject(:blacklist) { described_class.const_get(:USER_ATTRIBUTE_BLACKLIST) } subject(:filter) { described_class.const_get(:USER_ATTRIBUTE_FILTER) }
it 'contains sensitive attributes' do it 'contains sensitive attributes' do
expect(blacklist).to include('password') expect(filter).to include('password')
end end
end end
@ -26,7 +26,7 @@ RSpec.shared_examples 'TriggerWebhookJob::RecordPayload backend' do |factory|
end end
end end
it 'does not contain blacklisted User attributes' do it 'does not contain filtered User attributes' do
expect(generate['created_by']).not_to have_key('password') expect(generate['created_by']).not_to have_key('password')
end end
end end

View file

@ -131,7 +131,7 @@ RSpec.describe Ldap::User do
# selectable attribute # selectable attribute
ldap_entry['mail'] = 'test@example.com' ldap_entry['mail'] = 'test@example.com'
# blacklisted attribute # filtered attribute
ldap_entry['lastlogon'] = DateTime.current ldap_entry['lastlogon'] = DateTime.current
allow(mocked_ldap).to receive(:search).and_yield(ldap_entry) allow(mocked_ldap).to receive(:search).and_yield(ldap_entry)

View file

@ -6,16 +6,16 @@ require 'models/concerns/has_xss_sanitized_note_examples'
RSpec.describe Chat, type: :model do RSpec.describe Chat, type: :model do
it_behaves_like 'HasXssSanitizedNote', model_factory: :chat it_behaves_like 'HasXssSanitizedNote', model_factory: :chat
describe 'website whitelisting' do describe 'website allowing' do
let(:chat) { create(:chat, whitelisted_websites: 'zammad.org') } let(:chat) { create(:chat, allowed_websites: 'zammad.org') }
it 'detects whitelisted website' do it 'detects allowed website' do
result = chat.website_whitelisted?('https://www.zammad.org') result = chat.website_allowed?('https://www.zammad.org')
expect(result).to be true expect(result).to be true
end end
it 'detects non-whitelisted website' do it 'detects non-allowed website' do
result = chat.website_whitelisted?('https://www.zammad.com') result = chat.website_allowed?('https://www.zammad.com')
expect(result).to be false expect(result).to be false
end end
end end

View file

@ -2022,7 +2022,7 @@ RSpec.describe 'Ticket', type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response['result']).to eq('failed') expect(json_response['result']).to eq('failed')
expect(json_response['message']).to eq('No such master ticket number!') expect(json_response['message']).to eq('No such target ticket number!')
put "/api/v1/ticket_merge/#{ticket3.id}/#{ticket1.number}", params: {}, as: :json put "/api/v1/ticket_merge/#{ticket3.id}/#{ticket1.number}", params: {}, as: :json
expect(response).to have_http_status(:forbidden) expect(response).to have_http_status(:forbidden)
@ -2040,7 +2040,7 @@ RSpec.describe 'Ticket', type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response['result']).to eq('success') expect(json_response['result']).to eq('success')
expect(json_response['master_ticket']['id']).to eq(ticket2.id) expect(json_response['target_ticket']['id']).to eq(ticket2.id)
end end
it 'does ticket merge - change permission (07.02)' do it 'does ticket merge - change permission (07.02)' do
@ -2070,7 +2070,7 @@ RSpec.describe 'Ticket', type: :request do
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response['result']).to eq('success') expect(json_response['result']).to eq('success')
expect(json_response['master_ticket']['id']).to eq(ticket2.id) expect(json_response['target_ticket']['id']).to eq(ticket2.id)
end end
it 'does ticket search sorted (08.01)' do it 'does ticket search sorted (08.01)' do

View file

@ -62,7 +62,7 @@ class AgentTicketMergeTest < TestCase
modal_ready() modal_ready()
set( set(
css: '.modal input[name="master_ticket_number"]', css: '.modal input[name="target_ticket_number"]',
value: ticket1[:number], value: ticket1[:number],
) )
@ -116,7 +116,7 @@ class AgentTicketMergeTest < TestCase
modal_ready() modal_ready()
set( set(
css: '.modal input[name="master_ticket_number"]', css: '.modal input[name="target_ticket_number"]',
value: ticket3[:number], value: ticket3[:number],
) )
click( css: '.modal button[type="submit"]' ) click( css: '.modal button[type="submit"]' )