- Major refactoring of Zendesk import logic including introduction of RSpec tests.

- Fixed issue #487: No login possible after zendesk import.
This commit is contained in:
Thorsten Eckel 2016-12-19 09:59:54 +01:00
parent eb60ce2123
commit 4d209b805b
104 changed files with 3267 additions and 1068 deletions

View file

@ -31,7 +31,9 @@
<div class="wizard-body flex vertical justified">
<p>
<%- @T('Enter your Email address and the Zendesk API token gained from your') %> <a class="js-zendeskUrlApiToken" target="_blank"><%- @T('admin interface') %></a>
</p>
<p>
<%- @T('Attention: These will be your login credentials after the import is completed.') %>
</p>
<div class="form-group">
<label for="zendesk-email">Email address</label>

View file

@ -4,13 +4,25 @@ module Import
# rubocop:disable Style/ModuleFunction
extend self
def import_action(records, *args)
pre_import_hook(records)
import_loop(records) do |record|
next if skip?(record)
backend_instance = create_instance(record, *args)
post_import_hook(record, backend_instance)
end
end
def import(_records)
raise 'Missing implementation for import method for this factory'
raise 'Missing import method implementation for this factory'
end
def pre_import_hook(_records)
end
def post_import_hook(_record, _backend_instance)
end
def backend_class(_record)
"Import::#{module_name}".constantize
end
@ -21,6 +33,14 @@ module Import
private
def create_instance(record, *args)
backend_class(record).new(record, *args)
end
def import_loop(records, &import_block)
records.each(&import_block)
end
def module_name
name.to_s.sub(/Import::/, '').sub(/Factory/, '')
end

View file

@ -4,13 +4,6 @@ module Import
# rubocop:disable Style/ModuleFunction
extend self
def import(records)
pre_import_hook(records)
records.each do |record|
next if skip?(record)
backend_class(record).new(record)
end
end
alias import import_action
end
end

View file

@ -7,11 +7,7 @@ module Import
def import(records)
ActiveRecord::Base.transaction do
pre_import_hook(records)
records.each do |record|
next if skip?(record)
backend_class(record).new(record)
end
import_action(records)
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,65 @@
module Import
module Zendesk
module Async
# rubocop:disable Style/ModuleFunction
extend self
def start_bg
Setting.reload
Import::Zendesk.connection_test
# get statistic before starting import
statistic
# start thread to observe current state
status_update_thread = Thread.new {
loop do
result = {
data: current_state,
result: 'in_progress',
}
Cache.write('import:state', result, expires_in: 10.minutes)
sleep 8
end
}
sleep 2
# start import data
begin
Import::Zendesk.start
rescue => e
status_update_thread.exit
status_update_thread.join
Rails.logger.error e.message
Rails.logger.error e.backtrace.inspect
result = {
message: e.message,
result: 'error',
}
Cache.write('import:state', result, expires_in: 10.hours)
return false
end
sleep 16 # wait until new finished import state is on client
status_update_thread.exit
status_update_thread.join
result = {
result: 'import_done',
}
Cache.write('import:state', result, expires_in: 10.hours)
Setting.set('system_init_done', true)
Setting.set('import_mode', false)
end
def status_bg
state = Cache.get('import:state')
return state if state
{
message: 'not running',
}
end
end
end
end

View file

@ -0,0 +1,16 @@
module Import
module Zendesk
module BaseFactory
include Import::Factory
# rubocop:disable Style/ModuleFunction
extend self
private
def import_loop(records, &import_block)
records.all!(&import_block)
end
end
end
end

View file

@ -0,0 +1,21 @@
module Import
module Zendesk
class Group
include Import::Helper
attr_reader :zendesk_id, :id
def initialize(group)
local_group = ::Group.create_if_not_exists(
name: group.name,
active: !group.deleted,
updated_by_id: 1,
created_by_id: 1
)
@zendesk_id = group.id
@id = local_group.id
end
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
module GroupFactory
extend Import::Zendesk::BaseFactory
extend Import::Zendesk::LocalIDMapperHook
end
end
end

View file

@ -0,0 +1,19 @@
module Import
module Zendesk
module Helper
# rubocop:disable Style/ModuleFunction
extend self
private
def get_fields(zendesk_fields)
return {} if !zendesk_fields
fields = {}
zendesk_fields.each { |key, value|
fields[key] = value
}
fields
end
end
end
end

View file

@ -0,0 +1,61 @@
module Import
module Zendesk
module ImportStats
# rubocop:disable Style/ModuleFunction
extend self
def current_state
data = statistic
{
Group: {
done: ::Group.count,
total: data['Groups'] || 0,
},
Organization: {
done: ::Organization.count,
total: data['Organizations'] || 0,
},
User: {
done: ::User.count,
total: data['Users'] || 0,
},
Ticket: {
done: ::Ticket.count,
total: data['Tickets'] || 0,
},
}
end
def statistic
# check cache
cache = Cache.get('import_zendesk_stats')
return cache if cache
# retrive statistic
result = {
'Tickets' => 0,
'TicketFields' => 0,
'UserFields' => 0,
'OrganizationFields' => 0,
'Groups' => 0,
'Organizations' => 0,
'Users' => 0,
'GroupMemberships' => 0,
'Macros' => 0,
'Views' => 0,
'Automations' => 0,
}
result.each { |object, _score|
result[ object ] = Import::Zendesk::Requester.client.send( object.underscore.to_sym ).count!
}
Cache.write('import_zendesk_stats', result)
result
end
end
end
end

View file

@ -0,0 +1,25 @@
module Import
module Zendesk
module LocalIDMapperHook
# rubocop:disable Style/ModuleFunction
extend self
def local_id(zendesk_id)
init_mapping
@zendesk_mapping[ zendesk_id ]
end
def post_import_hook(_record, backend_instance)
init_mapping
@zendesk_mapping[ backend_instance.zendesk_id ] = backend_instance.id
end
private
def init_mapping
@zendesk_mapping ||= {}
end
end
end
end

View file

@ -0,0 +1,73 @@
module Import
module Zendesk
class ObjectAttribute
def initialize(object, name, attribute)
initialize_data_option(attribute)
init_callback(attribute)
add(object, name, attribute)
end
private
def init_callback(_attribute)
raise 'Missing init_callback method implementation for this object attribute'
end
def add(object, name, attribute)
ObjectManager::Attribute.add( attribute_config(object, name, attribute) )
ObjectManager::Attribute.migration_execute(false)
end
def attribute_config(object, name, attribute)
{
object: object,
name: name,
display: attribute.title,
data_type: data_type(attribute),
data_option: @data_option,
editable: !attribute.removable,
active: attribute.active,
screens: screens(attribute),
position: attribute.position,
created_by_id: 1,
updated_by_id: 1,
}
end
def screens(attribute)
config = {
view: {
'-all-' => {
shown: true,
},
}
}
return config if !attribute.visible_in_portal && attribute.required_in_portal
{
edit: {
Customer: {
shown: attribute.visible_in_portal,
null: !attribute.required_in_portal,
},
}.merge(config)
}
end
def initialize_data_option(attribute)
@data_option = {
null: !attribute.required,
note: attribute.description,
}
end
def data_type(attribute)
attribute.type
end
end
end
end

View file

@ -0,0 +1,23 @@
module Import
module Zendesk
class ObjectAttribute
class Checkbox < Import::Zendesk::ObjectAttribute
def init_callback(_object_attribte)
@data_option.merge!(
default: false,
options: {
true => 'yes',
false => 'no',
},
)
end
private
def data_type(_attribute)
'boolean'
end
end
end
end
end

View file

@ -0,0 +1,20 @@
# this require is required (hehe) because of Rails autoloading
# which causes strange behavior not inheriting correctly
# from Import::OTRS::DynamicField
require 'import/zendesk/object_attribute'
module Import
module Zendesk
class ObjectAttribute
class Date < Import::Zendesk::ObjectAttribute
def init_callback(_object_attribte)
@data_option.merge!(
future: true,
past: true,
diff: 0,
)
end
end
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
class ObjectAttribute
class Decimal < Import::Zendesk::ObjectAttribute::Text
end
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
class ObjectAttribute
class Dropdown < Import::Zendesk::ObjectAttribute::Select
end
end
end
end

View file

@ -0,0 +1,25 @@
# this require is required (hehe) because of Rails autoloading
# which causes strange behavior not inheriting correctly
# from Import::OTRS::DynamicField
require 'import/zendesk/object_attribute'
module Import
module Zendesk
class ObjectAttribute
class Integer < Import::Zendesk::ObjectAttribute
def init_callback(_object_attribte)
@data_option.merge!(
min: 0,
max: 999_999_999,
)
end
private
def data_type(_attribute)
'integer'
end
end
end
end
end

View file

@ -0,0 +1,26 @@
# this require is required (hehe) because of Rails autoloading
# which causes strange behavior not inheriting correctly
# from Import::OTRS::DynamicField
require 'import/zendesk/object_attribute'
module Import
module Zendesk
class ObjectAttribute
class Regexp < Import::Zendesk::ObjectAttribute
def init_callback(object_attribte)
@data_option.merge!(
type: 'text',
maxlength: 255,
regex: object_attribte.regexp_for_validation,
)
end
private
def data_type(_attribute)
'input'
end
end
end
end
end

View file

@ -0,0 +1,28 @@
module Import
module Zendesk
class ObjectAttribute
class Select < Import::Zendesk::ObjectAttribute
def init_callback(object_attribte)
@data_option.merge!(
default: '',
options: options(object_attribte),
)
end
private
def data_type(_attribute)
'select'
end
def options(object_attribte)
result = {}
object_attribte.custom_field_options.each { |entry|
result[ entry['value'] ] = entry['name']
}
result
end
end
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
class ObjectAttribute
class Tagger < Import::Zendesk::ObjectAttribute::Select
end
end
end
end

View file

@ -0,0 +1,20 @@
module Import
module Zendesk
class ObjectAttribute
class Text < Import::Zendesk::ObjectAttribute
def init_callback(_object_attribte)
@data_option.merge!(
type: 'text',
maxlength: 255,
)
end
private
def data_type(_attribute)
'input'
end
end
end
end
end

View file

@ -0,0 +1,20 @@
module Import
module Zendesk
class ObjectAttribute
class Textarea < Import::Zendesk::ObjectAttribute
def init_callback(_object_attribte)
@data_option.merge!(
type: 'textarea',
maxlength: 255,
)
end
private
def data_type(_attribute)
'input'
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Import
module Zendesk
class ObjectField
attr_reader :zendesk_id, :id
def initialize(object_field)
import(object_field)
@zendesk_id = object_field.id
@id = local_name(object_field)
end
private
def local_name(object_field)
return @local_name if @local_name
@local_name = remote_name(object_field).gsub(/\s/, '_').downcase
end
def remote_name(object_field)
object_field['key'] # TODO: y?!
end
def import(object_field)
backend_class(object_field).new(object_name, local_name(object_field), object_field)
end
def backend_class(object_field)
"Import::Zendesk::ObjectAttribute::#{object_field.type .capitalize}".constantize
end
def object_name
self.class.name.to_s.sub(/Import::Zendesk::/, '').sub(/Field/, '')
end
end
end
end

View file

@ -0,0 +1,34 @@
# https://developer.zendesk.com/rest_api/docs/core/organizations
module Import
module Zendesk
class Organization
include Import::Zendesk::Helper
attr_reader :zendesk_id, :id
def initialize(organization)
local_organization = ::Organization.create_if_not_exists(local_organization_fields(organization))
@zendesk_id = organization.id
@id = local_organization.id
end
private
def local_organization_fields(organization)
{
name: organization.name,
note: organization.note,
shared: organization.shared_tickets,
# shared: organization.shared_comments, # TODO, not yet implemented
# }.merge(organization.organization_fields) # TODO
updated_by_id: 1,
created_by_id: 1
}.merge(custom_fields(organization))
end
def custom_fields(organization)
get_fields(organization.organization_fields)
end
end
end
end

View file

@ -0,0 +1,10 @@
module Import
module Zendesk
module OrganizationFactory
# we need to loop over each instead of all!
# so we can use the default import factory here
extend Import::Factory
extend Import::Zendesk::LocalIDMapperHook
end
end
end

View file

@ -0,0 +1,6 @@
module Import
module Zendesk
class OrganizationField < Import::Zendesk::ObjectField
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
module OrganizationFieldFactory
extend Import::Zendesk::BaseFactory
extend Import::Zendesk::LocalIDMapperHook
end
end
end

View file

@ -0,0 +1,32 @@
module Import
module Zendesk
class Priority
MAPPING = {
'low' => '1 low',
nil => '2 normal',
'normal' => '2 normal',
'high' => '3 high',
'urgent' => '3 high',
}.freeze
class << self
def lookup(ticket)
remote_priority = ticket.priority
@mapping ||= {}
if @mapping[ remote_priority ]
return @mapping[ remote_priority ]
end
@mapping[ remote_priority ] = ::Ticket::Priority.lookup( name: map(remote_priority) )
end
private
def map(priority)
MAPPING.fetch(priority, MAPPING[nil])
end
end
end
end
end

View file

@ -0,0 +1,39 @@
module Import
module Zendesk
module Requester
# rubocop:disable Style/ModuleFunction
extend self
def connection_test
# make sure to reinitialize client
# to react to config changes
initialize_client
return true if client.users.first
false
end
def client
return @client if @client
initialize_client
@client
end
private
def initialize_client
@client = ZendeskAPI::Client.new do |config|
config.url = Setting.get('import_zendesk_endpoint')
# Basic / Token Authentication
config.username = Setting.get('import_zendesk_endpoint_username')
config.token = Setting.get('import_zendesk_endpoint_key')
# when hitting the rate limit, sleep automatically,
# then retry the request.
config.retry = true
end
end
end
end
end

View file

@ -0,0 +1,29 @@
module Import
module Zendesk
class State
MAPPING = {
'pending' => 'pending reminder',
'solved' => 'closed',
}.freeze
class << self
def lookup(ticket)
remote_state = ticket.status
@mapping ||= {}
if @mapping[ remote_state ]
return @mapping[ remote_state ]
end
@mapping[ remote_state ] = ::Ticket::State.lookup( name: map( remote_state ) )
end
private
def map(state)
MAPPING.fetch(state, state)
end
end
end
end
end

View file

@ -0,0 +1,73 @@
# https://developer.zendesk.com/rest_api/docs/core/tickets
# https://developer.zendesk.com/rest_api/docs/core/ticket_comments#ticket-comments
# https://developer.zendesk.com/rest_api/docs/core/ticket_audits#the-via-object
# https://developer.zendesk.com/rest_api/docs/help_center/article_attachments
# https://developer.zendesk.com/rest_api/docs/core/ticket_audits # v2
module Import
module Zendesk
class Ticket
include Import::Helper
def initialize(ticket)
create_or_update(ticket)
Import::Zendesk::Ticket::TagFactory.import(ticket.tags, @local_ticket, ticket)
Import::Zendesk::Ticket::CommentFactory.import(ticket.comments, @local_ticket, ticket)
end
private
def create_or_update(ticket)
mapped_ticket = local_ticket_fields(ticket)
return if updated?(mapped_ticket)
create(mapped_ticket)
end
def updated?(ticket)
@local_ticket = ::Ticket.find_by(id: ticket[:id])
return false if !@local_ticket
@local_ticket.update_attributes(ticket)
true
end
def create(ticket)
@local_ticket = ::Ticket.create(ticket)
reset_primary_key_sequence('tickets')
end
def local_ticket_fields(ticket)
local_user_id = Import::Zendesk::UserFactory.local_id( ticket.requester_id ) || 1
{
id: ticket.id,
title: ticket.subject,
note: ticket.description,
group_id: Import::Zendesk::GroupFactory.local_id( ticket.group_id ) || 1,
customer_id: local_user_id,
organization_id: Import::Zendesk::OrganizationFactory.local_id( ticket.organization_id ),
priority: Import::Zendesk::Priority.lookup(ticket),
state: Import::Zendesk::State.lookup(ticket),
pending_time: ticket.due_at,
updated_at: ticket.updated_at,
created_at: ticket.created_at,
updated_by_id: local_user_id,
created_by_id: local_user_id,
create_article_sender_id: Import::Zendesk::Ticket::Comment::Sender.local_id(local_user_id),
create_article_type_id: Import::Zendesk::Ticket::Comment::Type.local_id(ticket),
}.merge(custom_fields(ticket))
end
def custom_fields(ticket)
custom_fields = ticket.custom_fields
fields = {}
return fields if !custom_fields
custom_fields.each do |custom_field|
field_name = Import::Zendesk::TicketFieldFactory.local_id(custom_field['id'])
field_value = custom_field['value']
next if field_value.nil?
fields[ field_name.to_sym ] = field_value
end
fields
end
end
end
end

View file

@ -0,0 +1,71 @@
module Import
module Zendesk
class Ticket
class Comment
def initialize(comment, local_ticket, _zendesk_ticket)
create_or_update(comment, local_ticket)
import_attachments(comment)
end
private
def create_or_update(comment, local_ticket)
mapped_article = local_article_fields(comment, local_ticket)
return if updated?(mapped_article)
create(mapped_article)
end
def updated?(article)
@local_article = ::Ticket::Article.find_by(message_id: article[:message_id])
return false if !@local_article
@local_article.update_attributes(article)
true
end
def create(article)
@local_article = ::Ticket::Article.create(article)
end
def local_article_fields(comment, local_ticket)
local_user_id = Import::Zendesk::UserFactory.local_id( comment.author_id ) || 1
{
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: Import::Zendesk::Ticket::Comment::Sender.local_id( local_user_id ),
type_id: Import::Zendesk::Ticket::Comment::Type.local_id(comment),
}.merge(from_to(comment))
end
def from_to(comment)
if comment.via.channel == 'email'
{
from: comment.via.source.from.address,
to: comment.via.source.to.address # Notice comment.via.from.original_recipients = [\"another@gmail.com\", \"support@example.zendesk.com\"]
}
elsif comment.via.channel == 'facebook'
{
from: comment.via.source.from.facebook_id,
to: comment.via.source.to.facebook_id
}
else
{}
end
end
def import_attachments(comment)
attachments = comment.attachments
return if attachments.empty?
Import::Zendesk::Ticket::Comment::AttachmentFactory.import(attachments, @local_article)
end
end
end
end
end

View file

@ -0,0 +1,43 @@
module Import
module Zendesk
class Ticket
class Comment
class Attachment
extend Import::Helper
def initialize(attachment, local_article)
response = request(attachment)
return if !response
::Store.add(
object: 'Ticket::Article',
o_id: local_article.id,
data: response.body,
filename: attachment.file_name,
preferences: {
'Content-Type' => attachment.content_type
},
created_by_id: 1
)
end
private
def request(attachment)
response = UserAgent.get(
attachment.content_url,
{},
{
open_timeout: 10,
read_timeout: 60,
},
)
return response if response.success?
log response.error
end
end
end
end
end
end

View file

@ -0,0 +1,20 @@
module Import
module Zendesk
class Ticket
class Comment
module AttachmentFactory
# we need to loop over each instead of all!
# so we can use the default import factory here
extend Import::Factory
private
def create_instance(record, *args)
local_article = args[0]
backend_class(record).new(record, local_article)
end
end
end
end
end
end

View file

@ -0,0 +1,49 @@
module Import
module Zendesk
class Ticket
class Comment
module Sender
# rubocop:disable Style/ModuleFunction
extend self
def local_id(user_id)
author = author_lookup(user_id)
sender_id(author)
end
private
def author_lookup(user_id)
::User.find( user_id )
end
def sender_id(author)
if author.role?('Customer')
article_sender_customer
elsif author.role?('Agent')
article_sender_agent
else
article_sender_system
end
end
def article_sender_customer
return @article_sender_customer if @article_sender_customer
@article_sender_customer = ::Ticket::Article::Sender.lookup(name: 'Customer').id
end
def article_sender_agent
return @article_sender_agent if @article_sender_agent
@article_sender_agent = ::Ticket::Article::Sender.lookup(name: 'Agent').id
end
def article_sender_system
return @article_sender_system if @article_sender_system
@article_sender_system = ::Ticket::Article::Sender.lookup(name: 'System').id
end
end
end
end
end
end

View file

@ -0,0 +1,60 @@
module Import
module Zendesk
class Ticket
class Comment
module Type
# rubocop:disable Style/ModuleFunction
extend self
def local_id(object)
case object.via.channel
when 'web'
article_type_id[:web]
when 'email'
article_type_id[:email]
when 'sample_ticket'
article_type_id[:note]
when 'twitter'
if object.via.source.rel == 'mention'
article_type_id[:twitter_status]
else
article_type_id[:twitter_direct_message]
end
when 'facebook'
if object.via.source.rel == 'post'
article_type_id[:facebook_feed_post]
else
article_type_id[:facebook_feed_comment]
end
# fallback for other not (yet) supported article types
# See:
# https://support.zendesk.com/hc/en-us/articles/203661746-Zendesk-Glossary#topic_zie_aqe_tf
# https://support.zendesk.com/hc/en-us/articles/203661596-About-Zendesk-Support-channels
else
article_type_id[:web]
end
end
private
def article_type_id
return @article_type_id if @article_type_id
article_types = ['web', 'note', 'email', 'twitter status',
'twitter direct-message', 'facebook feed post',
'facebook feed comment']
@article_type_id = {}
article_types.each do |article_type|
article_type_key = article_type.gsub(/\s|\-/, '_').to_sym
@article_type_id[article_type_key] = ::Ticket::Article::Type.lookup(name: article_type).id
end
@article_type_id
end
end
end
end
end
end

View file

@ -0,0 +1,9 @@
module Import
module Zendesk
class Ticket
module CommentFactory
extend Import::Zendesk::Ticket::SubObjectFactory
end
end
end
end

View file

@ -0,0 +1,21 @@
module Import
module Zendesk
class Ticket
module SubObjectFactory
# we need to loop over each instead of all!
# so we can use the default import factory here
include Import::Factory
private
def create_instance(record, *args)
local_ticket = args[0]
zendesk_ticket = args[1]
backend_class(record).new(record, local_ticket, zendesk_ticket)
end
end
end
end
end

View file

@ -0,0 +1,16 @@
module Import
module Zendesk
class Ticket
class Tag
def initialize(tag, local_ticket, zendesk_ticket)
::Tag.tag_add(
object: 'Ticket',
o_id: local_ticket.id,
item: tag.id,
created_by_id: Import::Zendesk::UserFactory.local_id(zendesk_ticket.requester_id) || 1,
)
end
end
end
end
end

View file

@ -0,0 +1,9 @@
module Import
module Zendesk
class Ticket
module TagFactory
extend Import::Zendesk::Ticket::SubObjectFactory
end
end
end
end

View file

@ -0,0 +1,7 @@
module Import
module Zendesk
module TicketFactory
extend Import::Zendesk::BaseFactory
end
end
end

View file

@ -0,0 +1,12 @@
module Import
module Zendesk
class TicketField < Import::Zendesk::ObjectField
private
def remote_name(ticket_field)
ticket_field.title
end
end
end
end

View file

@ -0,0 +1,33 @@
module Import
module Zendesk
module TicketFieldFactory
extend Import::Zendesk::BaseFactory
extend Import::Zendesk::LocalIDMapperHook
MAPPING = {
'subject' => 'title',
'description' => 'note',
'status' => 'state_id',
'tickettype' => 'type',
'priority' => 'priority_id',
'group' => 'group_id',
'assignee' => 'owner_id',
}.freeze
# rubocop:disable Style/ModuleFunction
extend self
def skip?(field)
# check if the Ticket object already has a same named column / attribute
# so we want to skip instead of importing it
Ticket.column_names.include?( local_attribute(field) )
end
private
def local_attribute(field)
MAPPING.fetch(field.type, field.type)
end
end
end
end

View file

@ -0,0 +1,75 @@
# Rails autoload has some issues with same namend sub-classes
# in the importer folder require AND simultaniuos requiring
# of the same file in different threads so we need to
# require them ourself
require 'import/zendesk/user/group'
require 'import/zendesk/user/role'
# https://developer.zendesk.com/rest_api/docs/core/users
module Import
module Zendesk
class User
include Import::Zendesk::Helper
attr_reader :zendesk_id, :id
def initialize(user)
local_user = ::User.create_or_update( local_user_fields(user) )
@zendesk_id = user.id
@id = local_user.id
end
private
def local_user_fields(user)
{
login: login(user),
firstname: user.name,
email: user.email,
phone: user.phone,
password: password(user),
active: !user.suspended,
groups: Import::Zendesk::User::Group.for(user),
roles: roles(user),
note: user.notes,
verified: user.verified,
organization_id: Import::Zendesk::OrganizationFactory.local_id( user.organization_id ),
last_login: user.last_login_at,
image_source: photo(user),
updated_by_id: 1,
created_by_id: 1
}.merge(custom_fields(user))
end
def login(user)
return user.email if user.email
# Zendesk users may have no other identifier than the ID, e.g. twitter users
user.id.to_s
end
def password(user)
return Setting.get('import_zendesk_endpoint_key') if import_user?(user)
''
end
def roles(user)
return Import::Zendesk::User::Role.map(user, 'admin') if import_user?(user)
Import::Zendesk::User::Role.for(user)
end
def import_user?(user)
return false if user.email.blank?
user.email == Setting.get('import_zendesk_endpoint_username')
end
def photo(user)
return if !user.photo
user.photo.content_url
end
def custom_fields(user)
get_fields(user.user_fields)
end
end
end
end

View file

@ -0,0 +1,51 @@
# this require is required (hehe) because of Rails autoloading
# which causes strange behavior not inheriting correctly
# from Import::OTRS::DynamicField
require 'import/zendesk/user'
# https://developer.zendesk.com/rest_api/docs/core/groups
module Import
module Zendesk
class User
module Group
# rubocop:disable Style/ModuleFunction
extend self
def for(user)
groups = []
return groups if mapping[user.id].empty?
mapping[user.id].each { |zendesk_group_id|
local_group_id = Import::Zendesk::GroupFactory.local_id(zendesk_group_id)
next if !local_group_id
group = ::Group.find( local_group_id )
groups.push(group)
}
groups
end
private
def mapping
return @mapping if !@mapping.nil?
@mapping = {}
Import::Zendesk::Requester.client.group_memberships.all! { |group_membership|
@mapping[ group_membership.user_id ] ||= []
@mapping[ group_membership.user_id ].push( group_membership.group_id )
}
@mapping
end
end
end
end
end

View file

@ -0,0 +1,62 @@
# this require is required (hehe) because of Rails autoloading
# which causes strange behavior not inheriting correctly
# from Import::OTRS::DynamicField
require 'import/zendesk/user'
module Import
module Zendesk
class User
module Role
extend Import::Helper
# rubocop:disable Style/ModuleFunction
extend self
def for(user)
map(user, group_method( user.role.name ))
end
def map(user, role)
send(role.to_sym, user)
rescue NoMethodError => e
log "Unknown mapping for role '#{user.role.name}' and user with id '#{user.id}'"
[]
end
private
def end_user(_user)
[role_customer]
end
def agent(user)
return [ role_agent ] if user.restricted_agent
admin(user)
end
def admin(_user)
[role_admin, role_agent]
end
def group_method(role)
role.tr('-', '_')
end
def role_admin
return @role_admin if @role_admin
@role_admin = ::Role.lookup(name: 'Admin')
end
def role_agent
return @role_agent if @role_agent
@role_agent = ::Role.lookup(name: 'Agent')
end
def role_customer
return @role_customer if @role_customer
@role_customer = ::Role.lookup(name: 'Customer')
end
end
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
module UserFactory
extend Import::Zendesk::BaseFactory
extend Import::Zendesk::LocalIDMapperHook
end
end
end

View file

@ -0,0 +1,6 @@
module Import
module Zendesk
class UserField < Import::Zendesk::ObjectField
end
end
end

View file

@ -0,0 +1,8 @@
module Import
module Zendesk
module UserFieldFactory
extend Import::Zendesk::BaseFactory
extend Import::Zendesk::LocalIDMapperHook
end
end
end

View file

@ -1,4 +1,4 @@
RSpec.shared_examples 'Import::OTRS::Async' do
RSpec.shared_examples 'Import::Async' do
it 'responds to start_bg' do
expect(described_class).to respond_to('start_bg')
end

View file

@ -6,6 +6,9 @@ RSpec.shared_examples 'Import::BaseFactory' do
it 'responds to pre_import_hook' do
expect(described_class).to respond_to('pre_import_hook')
end
it 'responds to post_import_hook' do
expect(described_class).to respond_to('post_import_hook')
end
it 'responds to backend_class' do
expect(described_class).to respond_to('backend_class')
end
@ -18,7 +21,7 @@ RSpec.shared_examples 'Import::BaseFactory extender' do
it 'calls new on determined backend object' do
record = double()
expect(described_class).to receive(:backend_class).and_return(Class)
expect(Class).to receive(:new).with(record)
expect(Class).to receive(:new).with(record, any_args)
described_class.import([record])
end
end

View file

@ -1,4 +1,4 @@
RSpec.shared_examples 'Import::OTRS::ImportStats' do
RSpec.shared_examples 'Import::ImportStats' do
it 'responds to current_state' do
expect(described_class).to respond_to('current_state')
end

View file

@ -3,4 +3,13 @@ require 'import/factory_examples'
RSpec.describe Import::OTRS::PriorityFactory do
it_behaves_like 'Import::Factory'
it 'imports records' do
import_data = {
name: 'test',
}
expect(::Import::OTRS::Priority).to receive(:new).with(import_data)
described_class.import([import_data])
end
end

View file

@ -1,14 +1,14 @@
require 'rails_helper'
require 'import/helper_examples'
require 'import/importer_examples'
require 'import/otrs/async_examples'
require 'import/otrs/diff_examples'
require 'import/otrs/import_stats_examples'
require 'import/async_examples'
require 'import/import_stats_examples'
RSpec.describe Import::OTRS do
it_behaves_like 'Import backend'
it_behaves_like 'Import::Async'
it_behaves_like 'Import::Helper'
it_behaves_like 'Import::OTRS::Async'
it_behaves_like 'Import::ImportStats'
it_behaves_like 'Import::OTRS::Diff'
it_behaves_like 'Import::OTRS::ImportStats'
end

View file

@ -0,0 +1,23 @@
require 'import/factory_examples'
RSpec.shared_examples 'Import::Zendesk::BaseFactory' do
it_behaves_like 'Import::Factory'
it 'calls .all! on parameter object' do
parameter = double()
expect(parameter).to receive('all!')
described_class.import(parameter)
end
it 'calls new on determined backend object' do
expect(described_class).to receive(:backend_class).and_return(Class)
expect(described_class).to receive('skip?')
expect(described_class).to receive(:pre_import_hook)
expect(described_class).to receive(:post_import_hook)
record = double()
expect(Class).to receive(:new).with(record, any_args)
parameter = double()
expect(parameter).to receive('all!').and_yield(record)
described_class.import(parameter)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
RSpec.describe Import::Zendesk::BaseFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::GroupFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,25 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::Group do
it 'creates a group if not exists' do
group = double(
id: 31_337,
name: 'Test Group',
deleted: true
)
local_group = instance_double(::Group, id: 1337)
expect(::Group).to receive(:create_if_not_exists).with(hash_including(name: group.name, active: !group.deleted)).and_return(local_group)
created_instance = described_class.new(group)
expect(created_instance).to respond_to(:id)
expect(created_instance.id).to eq(local_group.id)
expect(created_instance).to respond_to(:zendesk_id)
expect(created_instance.zendesk_id).to eq(group.id)
end
end

View file

@ -0,0 +1,20 @@
RSpec.shared_examples 'Import::Zendesk::LocalIDMapperHook' do
it 'responds to local_id' do
expect(described_class).to respond_to('local_id')
end
it 'responds to post_import_hook' do
expect(described_class).to respond_to('post_import_hook')
end
it 'stores an ID mapping and makes it accessable' do
backend_instance = double(
zendesk_id: 31_337,
id: 1337,
)
described_class.post_import_hook(nil, backend_instance)
expect(described_class.local_id(backend_instance.zendesk_id)).to eq(backend_instance.id)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::LocalIDMapperHook do
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,5 @@
RSpec.shared_examples 'Lookup backend' do
it 'responds to lookup' do
expect(described_class).to respond_to('lookup')
end
end

View file

@ -0,0 +1,58 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Checkbox do
it 'imports boolean object attribute from checkbox object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'checkbox',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'boolean',
data_option: {
null: false,
note: 'Example attribute description',
default: false,
options: {
true => 'yes',
false => 'no'
}
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,59 @@
require 'rails_helper'
# required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/date'
RSpec.describe Import::Zendesk::ObjectAttribute::Date do
it 'imports date object attribute from date object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'date',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'date',
data_option: {
null: false,
note: 'Example attribute description',
future: true,
past: true,
diff: 0,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,55 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Decimal do
it 'imports input object attribute from decimal object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'decimal',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'input',
data_option: {
null: false,
note: 'Example attribute description',
type: 'text',
maxlength: 255,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,70 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Dropdown do
it 'imports select object attribute from dropdown object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'dropdown',
custom_field_options: [
{
'id' => 1,
'value' => 'Key 1',
'name' => 'Value 1'
},
{
'id' => 2,
'value' => 'Key 2',
'name' => 'Value 2'
},
]
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'select',
data_option: {
null: false,
note: 'Example attribute description',
default: '',
options: {
'Key 1' => 'Value 1',
'Key 2' => 'Value 2'
},
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
created_instance = described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,58 @@
require 'rails_helper'
# required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/integer'
RSpec.describe Import::Zendesk::ObjectAttribute::Integer do
it 'imports integer object attribute from integer object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'integer',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'integer',
data_option: {
null: false,
note: 'Example attribute description',
min: 0,
max: 999_999_999,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,61 @@
require 'rails_helper'
# required due to some of rails autoloading issues
require 'import/zendesk/object_attribute/regexp'
RSpec.describe Import::Zendesk::ObjectAttribute::Regexp do
it 'imports input object attribute from regexp object field' do
regex = '.+?'
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'regexp',
regexp_for_validation: regex
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'input',
data_option: {
null: false,
note: 'Example attribute description',
type: 'text',
maxlength: 255,
regex: regex,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,70 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Tagger do
it 'imports select object attribute from tagger object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'tagger',
custom_field_options: [
{
'id' => 1,
'value' => 'Key 1',
'name' => 'Value 1'
},
{
'id' => 2,
'value' => 'Key 2',
'name' => 'Value 2'
},
]
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'select',
data_option: {
null: false,
note: 'Example attribute description',
default: '',
options: {
'Key 1' => 'Value 1',
'Key 2' => 'Value 2'
},
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
created_instance = described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,55 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Text do
it 'imports input object attribute from text object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'text',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'input',
data_option: {
null: false,
note: 'Example attribute description',
type: 'text',
maxlength: 255,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,55 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute::Textarea do
it 'imports input object attribute from textarea object field' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'textarea',
)
expected_structure = {
object: 'Ticket',
name: 'example_field',
display: 'Example attribute',
data_type: 'input',
data_option: {
null: false,
note: 'Example attribute description',
type: 'textarea',
maxlength: 255,
},
editable: true,
active: true,
screens: {
edit: {
Customer: {
shown: true,
null: false
},
view: {
'-all-' => {
shown: true
}
}
}
},
position: 12,
created_by_id: 1,
updated_by_id: 1
}
expect(ObjectManager::Attribute).to receive(:add).with(expected_structure)
expect(ObjectManager::Attribute).to receive(:migration_execute)
described_class.new('Ticket', 'example_field', attribute)
end
end

View file

@ -0,0 +1,21 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::ObjectAttribute do
it 'throws an exception if no init_callback is implemented' do
attribute = double(
title: 'Example attribute',
description: 'Example attribute description',
removable: false,
active: true,
position: 12,
visible_in_portal: true,
required_in_portal: true,
required: true,
type: 'input',
)
expect { described_class.new('Ticket', 'example_field', attribute) }.to raise_error(RuntimeError)
end
end

View file

@ -0,0 +1,22 @@
RSpec.shared_examples 'Import::Zendesk::ObjectField' do
it 'initializes a object field backend import' do
object_field = double(id: 31_337, title: 'Example Field')
allow(object_field).to receive(:[]).with('key').and_return(object_field.title)
dummy_instance = double()
local_name = 'example_field'
dummy_backend = instance_double(Class)
expect(dummy_backend).to receive(:new).with(kind_of(String), local_name, object_field).and_return(dummy_instance)
expect_any_instance_of(described_class).to receive(:backend_class).and_return(dummy_backend)
created_instance = described_class.new(object_field)
expect(created_instance).to respond_to(:id)
expect(created_instance.id).to eq(local_name)
expect(created_instance).to respond_to(:zendesk_id)
expect(created_instance.zendesk_id).to eq(object_field.id)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/object_field_examples'
RSpec.describe Import::Zendesk::ObjectField do
it_behaves_like 'Import::Zendesk::ObjectField'
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::OrganizationFieldFactory do
it_behaves_like 'Import::Factory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::OrganizationFieldFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/object_field_examples'
RSpec.describe Import::Zendesk::OrganizationField do
it_behaves_like 'Import::Zendesk::ObjectField'
end

View file

@ -0,0 +1,27 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::Organization do
it 'creates a organization if not exists' do
organization = double(
id: 31_337,
name: 'Test Organization',
note: 'Test Organization note',
shared_tickets: true,
organization_fields: nil
)
local_organization = instance_double(::Organization, id: 1337)
expect(::Organization).to receive(:create_if_not_exists).with(hash_including(name: organization.name, note: organization.note, shared: organization.shared_tickets)).and_return(local_organization)
created_instance = described_class.new(organization)
expect(created_instance).to respond_to(:id)
expect(created_instance).to respond_to(:zendesk_id)
expect(created_instance.id).to eq(local_organization.id)
expect(created_instance.zendesk_id).to eq(organization.id)
end
end

View file

@ -0,0 +1,14 @@
require 'rails_helper'
require 'import/zendesk/lookup_backend_examples'
RSpec.describe Import::Zendesk::Priority do
it_behaves_like 'Lookup backend'
it 'looks up ticket priority' do
ticket = double(priority: nil)
dummy_result = 'dummy result'
expect(::Ticket::Priority).to receive(:lookup).and_return(dummy_result)
expect(described_class.lookup(ticket)).to eq(dummy_result)
end
end

View file

@ -0,0 +1,14 @@
require 'rails_helper'
require 'import/zendesk/lookup_backend_examples'
RSpec.describe Import::Zendesk::State do
it_behaves_like 'Lookup backend'
it 'looks up ticket state' do
ticket = double(status: nil)
dummy_result = 'dummy result'
expect(::Ticket::State).to receive(:lookup).and_return(dummy_result)
expect(described_class.lookup(ticket)).to eq(dummy_result)
end
end

View file

@ -0,0 +1,19 @@
require 'rails_helper'
require 'import/factory_examples'
RSpec.describe Import::Zendesk::Ticket::Comment::AttachmentFactory do
it_behaves_like 'Import::Factory'
it 'tunnels attachment and local article to backend' do
expect(described_class).to receive(:backend_class).and_return(Class)
expect(described_class).to receive('skip?')
expect(described_class).to receive(:pre_import_hook)
expect(described_class).to receive(:post_import_hook)
record = double()
local_article = double()
expect(Class).to receive(:new).with(record, local_article)
parameter = double()
expect(parameter).to receive(:each).and_yield(record)
described_class.import(parameter, local_article)
end
end

View file

@ -0,0 +1,37 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::Ticket::Comment::Attachment do
it 'downloads and stores attachments' do
local_article = double(id: 1337)
attachment = double(
file_name: 'Example.zip',
content_type: 'application/zip',
content_url: 'https://dl.remote.tld/394r0eifwskfjwlw3slf'
)
response = double(
body: 'content',
success?: true,
)
expect(UserAgent).to receive(:get).with(attachment.content_url, any_args).and_return(response)
add_args = {
object: 'Ticket::Article',
o_id: local_article.id,
data: response.body,
filename: attachment.file_name,
preferences: {
'Content-Type' => attachment.content_type
},
created_by_id: 1
}
expect(Store).to receive(:add).with(add_args)
described_class.new(attachment, local_article)
end
end

View file

@ -0,0 +1,7 @@
require 'rails_helper'
RSpec.shared_examples 'local_id lookup backend' do
it 'responds to local_id' do
expect(described_class).to respond_to(:local_id)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/ticket/comment/local_id_lookup_backend_examples'
RSpec.describe Import::Zendesk::Ticket::Comment::Sender do
it_behaves_like 'local_id lookup backend'
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/ticket/comment/local_id_lookup_backend_examples'
RSpec.describe Import::Zendesk::Ticket::Comment::Type do
it_behaves_like 'local_id lookup backend'
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/ticket/sub_object_factory_examples'
RSpec.describe Import::Zendesk::Ticket::CommentFactory do
it_behaves_like 'Import::Zendesk::Ticket::SubObjectFactory'
end

View file

@ -0,0 +1,637 @@
require 'rails_helper'
RSpec.describe Import::Zendesk::Ticket::Comment do
context 'email' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'email',
source: double(
from: double(address: 'from@sender.tld'),
to: double(address: 'to@receiver.tld')
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
from: comment.via.source.from.address,
to: comment.via.source.to.address,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 1,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'email',
source: double(
from: double(address: 'from@sender.tld'),
to: double(address: 'to@receiver.tld')
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
from: comment.via.source.from.address,
to: comment.via.source.to.address,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 1,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
context 'facebook' do
context 'post' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'facebook',
source: double(
from: double(facebook_id: 3_129_033),
to: double(facebook_id: 1_230_920),
rel: 'post',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
from: comment.via.source.from.facebook_id,
to: comment.via.source.to.facebook_id,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 8,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'facebook',
source: double(
from: double(facebook_id: 3_129_033),
to: double(facebook_id: 1_230_920),
rel: 'post',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
from: comment.via.source.from.facebook_id,
to: comment.via.source.to.facebook_id,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 8,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
context 'comment' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'facebook',
source: double(
from: double(facebook_id: 3_129_033),
to: double(facebook_id: 1_230_920),
rel: 'comment',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
from: comment.via.source.from.facebook_id,
to: comment.via.source.to.facebook_id,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 9,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'facebook',
source: double(
from: double(facebook_id: 3_129_033),
to: double(facebook_id: 1_230_920),
rel: 'comment',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
from: comment.via.source.from.facebook_id,
to: comment.via.source.to.facebook_id,
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 9,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
end
context 'twitter' do
context 'mention' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'twitter',
source: double(
rel: 'mention',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 6,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'twitter',
source: double(
rel: 'mention',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 6,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
context 'direct_message' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'twitter',
source: double(
rel: 'direct_message',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 7,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'twitter',
source: double(
rel: 'direct_message',
),
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 7,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
end
context 'web' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'web',
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 11,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'web',
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 11,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
context 'sample_ticket' do
it 'creates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'sample_ticket',
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
create_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 10,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(create_structure[:sender_id])
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id)
expect(::Ticket::Article).to receive(:create).with(create_structure)
described_class.new(comment, local_ticket, nil)
end
it 'updates' do
comment = double(
id: 1337,
author_id: 42,
public: true,
html_body: '<div>Hello World!</div>',
via: double(
channel: 'sample_ticket',
),
attachments: []
)
local_ticket = double(id: 31_337)
local_user_id = 99
update_structure = {
ticket_id: local_ticket.id,
body: comment.html_body,
content_type: 'text/html',
internal: !comment.public,
message_id: comment.id,
updated_by_id: local_user_id,
created_by_id: local_user_id,
sender_id: 23,
type_id: 10,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( comment.author_id ).and_return(local_user_id)
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(update_structure[:sender_id])
local_article = double()
expect(local_article).to receive(:update_attributes).with(update_structure)
expect(::Ticket::Article).to receive(:find_by).with(message_id: comment.id).and_return(local_article)
described_class.new(comment, local_ticket, nil)
end
end
end

View file

@ -0,0 +1,21 @@
require 'rails_helper'
require 'import/factory_examples'
RSpec.shared_examples 'Import::Zendesk::Ticket::SubObjectFactory' do
it_behaves_like 'Import::Factory'
it 'tunnels local and remote ticket object to backend' do
expect(described_class).to receive(:backend_class).and_return(Class)
expect(described_class).to receive('skip?')
expect(described_class).to receive(:pre_import_hook)
expect(described_class).to receive(:post_import_hook)
record = double()
local_ticket = double()
zendesk_ticket = double()
expect(Class).to receive(:new).with(record, local_ticket, zendesk_ticket)
parameter = double()
expect(parameter).to receive(:each).and_yield(record)
described_class.import(parameter, local_ticket, zendesk_ticket)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/ticket/sub_object_factory_examples'
RSpec.describe Import::Zendesk::Ticket::TagFactory do
it_behaves_like 'Import::Zendesk::Ticket::SubObjectFactory'
end

View file

@ -0,0 +1,21 @@
require 'rails_helper'
# required due to some of rails autoloading issues
require 'import/zendesk/ticket/tag'
RSpec.describe Import::Zendesk::Ticket::Tag do
it 'creates ticket tags' do
tag = double(id: 'Test Tag')
local_ticket = instance_double(::Ticket, id: 1337)
zendesk_ticket = double(requester_id: 31_337)
expect(Import::Zendesk::UserFactory).to receive(:local_id).with(zendesk_ticket.requester_id)
expect(::Tag).to receive(:tag_add).with(hash_including(item: tag.id, o_id: local_ticket.id))
described_class.new(tag, local_ticket, zendesk_ticket)
end
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
RSpec.describe Import::Zendesk::TicketFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::TicketFieldFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/object_field_examples'
RSpec.describe Import::Zendesk::TicketField do
it_behaves_like 'Import::Zendesk::ObjectField'
end

View file

@ -0,0 +1,123 @@
require 'rails_helper'
# required due to some of rails autoloading issues
require 'import/zendesk/ticket'
RSpec.describe Import::Zendesk::Ticket do
it 'creates' do
ticket = double(
id: 1337,
subject: 'The ticket title',
description: 'An example ticket',
requester_id: 42,
group_id: 909,
organization_id: 101,
due_at: DateTime.tomorrow,
updated_at: DateTime.yesterday,
created_at: DateTime.yesterday,
tags: [],
comments: [],
custom_fields: [],
)
local_user_id = 23
expected_structure = {
id: ticket.id,
title: ticket.subject,
note: ticket.description,
group_id: 3,
customer_id: local_user_id,
organization_id: 89,
state: 13,
priority: 7,
pending_time: ticket.due_at,
updated_at: ticket.updated_at,
created_at: ticket.created_at,
updated_by_id: local_user_id,
created_by_id: local_user_id,
create_article_sender_id: 21,
create_article_type_id: 555,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( ticket.requester_id ).and_return(local_user_id)
expect(Import::Zendesk::GroupFactory).to receive(:local_id).with( ticket.group_id ).and_return(expected_structure[:group_id])
expect(Import::Zendesk::OrganizationFactory).to receive(:local_id).with( ticket.organization_id ).and_return(expected_structure[:organization_id])
expect(Import::Zendesk::Priority).to receive(:lookup).with(ticket).and_return(expected_structure[:priority])
expect(Import::Zendesk::State).to receive(:lookup).with(ticket).and_return(expected_structure[:state])
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(expected_structure[:create_article_sender_id])
expect(Import::Zendesk::Ticket::Comment::Type).to receive(:local_id).with(ticket).and_return(expected_structure[:create_article_type_id])
local_ticket = double()
expect(Import::Zendesk::Ticket::TagFactory).to receive(:import).with(ticket.tags, local_ticket, ticket)
expect(Import::Zendesk::Ticket::CommentFactory).to receive(:import).with(ticket.comments, local_ticket, ticket)
expect(::Ticket).to receive(:find_by).with(id: expected_structure[:id])
expect(::Ticket).to receive(:create).with(expected_structure).and_return(local_ticket)
expect_any_instance_of(described_class).to receive(:reset_primary_key_sequence)
created_instance = described_class.new(ticket)
end
it 'updates' do
ticket = double(
id: 1337,
subject: 'The ticket title',
description: 'An example ticket',
requester_id: 42,
group_id: 909,
organization_id: 101,
due_at: DateTime.tomorrow,
updated_at: DateTime.yesterday,
created_at: DateTime.yesterday,
tags: [],
comments: [],
custom_fields: [],
)
local_user_id = 23
expected_structure = {
id: ticket.id,
title: ticket.subject,
note: ticket.description,
group_id: 3,
customer_id: local_user_id,
organization_id: 89,
state: 13,
priority: 7,
pending_time: ticket.due_at,
updated_at: ticket.updated_at,
created_at: ticket.created_at,
updated_by_id: local_user_id,
created_by_id: local_user_id,
create_article_sender_id: 21,
create_article_type_id: 555,
}
expect(Import::Zendesk::UserFactory).to receive(:local_id).with( ticket.requester_id ).and_return(local_user_id)
expect(Import::Zendesk::GroupFactory).to receive(:local_id).with( ticket.group_id ).and_return(expected_structure[:group_id])
expect(Import::Zendesk::OrganizationFactory).to receive(:local_id).with( ticket.organization_id ).and_return(expected_structure[:organization_id])
expect(Import::Zendesk::Priority).to receive(:lookup).with(ticket).and_return(expected_structure[:priority])
expect(Import::Zendesk::State).to receive(:lookup).with(ticket).and_return(expected_structure[:state])
expect(Import::Zendesk::Ticket::Comment::Sender).to receive(:local_id).with(local_user_id).and_return(expected_structure[:create_article_sender_id])
expect(Import::Zendesk::Ticket::Comment::Type).to receive(:local_id).with(ticket).and_return(expected_structure[:create_article_type_id])
local_ticket = double()
expect(Import::Zendesk::Ticket::TagFactory).to receive(:import).with(ticket.tags, local_ticket, ticket)
expect(Import::Zendesk::Ticket::CommentFactory).to receive(:import).with(ticket.comments, local_ticket, ticket)
expect(::Ticket).to receive(:find_by).with(id: expected_structure[:id]).and_return(local_ticket)
expect(local_ticket).to receive(:update_attributes).with(expected_structure)
created_instance = described_class.new(ticket)
end
end

View file

@ -0,0 +1,9 @@
require 'rails_helper'
require 'import/zendesk/user/lookup_backend_examples'
# required due to some of rails autoloading issues
require 'import/zendesk/user/group'
RSpec.describe Import::Zendesk::User::Group do
it_behaves_like 'lookup backend'
end

View file

@ -0,0 +1,7 @@
require 'rails_helper'
RSpec.shared_examples 'lookup backend' do
it 'responds to for' do
expect(described_class).to respond_to(:for)
end
end

View file

@ -0,0 +1,13 @@
require 'rails_helper'
require 'import/zendesk/user/lookup_backend_examples'
# required due to some of rails autoloading issues
require 'import/zendesk/user/role'
RSpec.describe Import::Zendesk::User::Role do
it_behaves_like 'lookup backend'
it 'responds to map' do
expect(described_class).to respond_to(:map)
end
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::UserFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,8 @@
require 'rails_helper'
require 'import/zendesk/base_factory_examples'
require 'import/zendesk/local_id_mapper_hook_examples'
RSpec.describe Import::Zendesk::UserFieldFactory do
it_behaves_like 'Import::Zendesk::BaseFactory'
it_behaves_like 'Import::Zendesk::LocalIDMapperHook'
end

View file

@ -0,0 +1,6 @@
require 'rails_helper'
require 'import/zendesk/object_field_examples'
RSpec.describe Import::Zendesk::UserField do
it_behaves_like 'Import::Zendesk::ObjectField'
end

Some files were not shown because too many files have changed in this diff Show more