Improved article attachment api (implemented easier attaching attachments via rest).

This commit is contained in:
Martin Edenhofer 2017-03-10 06:34:51 +01:00
parent 5eb49443dd
commit 1b3d787c27
7 changed files with 723 additions and 69 deletions

View file

@ -46,23 +46,22 @@ module CreatesTicketArticles
article.ticket_id = ticket.id article.ticket_id = ticket.id
# store dataurl images to store # store dataurl images to store
if form_id && article.body && article.content_type =~ %r{text/html}i attachments_inline = []
article.body.gsub!( %r{(<img\s.+?src=")(data:image/(jpeg|png);base64,.+?)">}i ) { |_item| if article.body && article.content_type =~ %r{text/html}i
article.body.gsub!( %r{(<img\s.?src=")(data:image/(jpeg|png);base64,.+?)"(|.+?)>}im ) { |_item|
file_attributes = StaticAssets.data_url_attributes($2) file_attributes = StaticAssets.data_url_attributes($2)
cid = "#{ticket.id}.#{form_id}.#{rand(999_999)}@#{Setting.get('fqdn')}" cid = "#{ticket.id}.#{rand(999_999_999)}@#{Setting.get('fqdn')}"
headers_store = { attachment = {
data: file_attributes[:content],
filename: cid,
preferences: {
'Content-Type' => file_attributes[:mime_type], 'Content-Type' => file_attributes[:mime_type],
'Mime-Type' => file_attributes[:mime_type], 'Mime-Type' => file_attributes[:mime_type],
'Content-ID' => cid, 'Content-ID' => cid,
'Content-Disposition' => 'inline', 'Content-Disposition' => 'inline',
},
} }
store = Store.add( attachments_inline.push attachment
object: 'UploadCache',
o_id: form_id,
data: file_attributes[:content],
filename: cid,
preferences: headers_store
)
"#{$1}cid:#{cid}\">" "#{$1}cid:#{cid}\">"
} }
end end
@ -76,6 +75,48 @@ module CreatesTicketArticles
end end
article.save! article.save!
# store inline attachments
attachments_inline.each { |attachment|
Store.add(
object: 'Ticket::Article',
o_id: article.id,
data: attachment[:data],
filename: attachment[:filename],
preferences: attachment[:preferences],
)
}
# add attachments as param
if params[:attachments]
params[:attachments].each_with_index { |attachment, index|
# validation
['mime-type', 'filename', 'data'].each { |key|
next if attachment[key]
raise Exceptions::UnprocessableEntity, "Attachment needs '#{key}' param for attachment with index '#{index}'"
}
preferences = {}
['charset', 'mime-type'].each { |key|
next if !attachment[key]
store_key = key.tr('-', '_').camelize.gsub(/(.+)([A-Z])/, '\1_\2').tr('_', '-')
preferences[store_key] = attachment[key]
}
if attachment[:data] !~ %r{^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$}
raise Exceptions::UnprocessableEntity, "Invalid base64 for attachment with index '#{index}'"
end
Store.add(
object: 'Ticket::Article',
o_id: article.id,
data: Base64.decode64(attachment[:data]),
filename: attachment[:filename],
preferences: preferences,
)
}
end
# account time # account time
if time_unit.present? if time_unit.present?
Ticket::TimeAccounting.create!( Ticket::TimeAccounting.create!(
@ -85,9 +126,9 @@ module CreatesTicketArticles
) )
end end
# remove attachments from upload cache
return article if !form_id return article if !form_id
# remove attachments from upload cache
Store.remove( Store.remove(
object: 'UploadCache', object: 'UploadCache',
o_id: form_id, o_id: form_id,
@ -95,4 +136,5 @@ module CreatesTicketArticles
article article
end end
end end

View file

@ -14,14 +14,11 @@ class TicketArticlesController < ApplicationController
# GET /articles/1 # GET /articles/1
def show def show
# permission check
article = Ticket::Article.find(params[:id]) article = Ticket::Article.find(params[:id])
article_permission(article) article_permission(article)
if params[:expand] if params[:expand]
result = article.attributes_with_association_names result = article.attributes_with_association_names
result[:attachments] = article.attachments
render json: result, status: :ok render json: result, status: :ok
return return
end end
@ -37,8 +34,6 @@ class TicketArticlesController < ApplicationController
# GET /ticket_articles/by_ticket/1 # GET /ticket_articles/by_ticket/1
def index_by_ticket def index_by_ticket
# permission check
ticket = Ticket.find(params[:id]) ticket = Ticket.find(params[:id])
ticket_permission(ticket) ticket_permission(ticket)
@ -50,9 +45,6 @@ class TicketArticlesController < ApplicationController
# ignore internal article if customer is requesting # ignore internal article if customer is requesting
next if article.internal == true && current_user.permissions?('ticket.customer') next if article.internal == true && current_user.permissions?('ticket.customer')
result = article.attributes_with_association_names result = article.attributes_with_association_names
# add attachments
result[:attachments] = article.attachments
articles.push result articles.push result
} }
@ -95,7 +87,6 @@ class TicketArticlesController < ApplicationController
if params[:expand] if params[:expand]
result = article.attributes_with_association_names result = article.attributes_with_association_names
result[:attachments] = article.attachments
render json: result, status: :created render json: result, status: :created
return return
end end
@ -111,8 +102,6 @@ class TicketArticlesController < ApplicationController
# PUT /articles/1 # PUT /articles/1
def update def update
# permission check
article = Ticket::Article.find(params[:id]) article = Ticket::Article.find(params[:id])
article_permission(article) article_permission(article)
@ -127,7 +116,6 @@ class TicketArticlesController < ApplicationController
if params[:expand] if params[:expand]
result = article.attributes_with_association_names result = article.attributes_with_association_names
result[:attachments] = article.attachments
render json: result, status: :ok render json: result, status: :ok
return return
end end
@ -220,8 +208,6 @@ class TicketArticlesController < ApplicationController
# GET /ticket_attachment/:ticket_id/:article_id/:id # GET /ticket_attachment/:ticket_id/:article_id/:id
def attachment def attachment
# permission check
ticket = Ticket.lookup(id: params[:ticket_id]) ticket = Ticket.lookup(id: params[:ticket_id])
if !ticket_permission(ticket) if !ticket_permission(ticket)
raise Exceptions::NotAuthorized, 'No such ticket.' raise Exceptions::NotAuthorized, 'No such ticket.'
@ -255,8 +241,6 @@ class TicketArticlesController < ApplicationController
# GET /ticket_article_plain/1 # GET /ticket_article_plain/1
def article_plain def article_plain
# permission check
article = Ticket::Article.find(params[:id]) article = Ticket::Article.find(params[:id])
article_permission(article) article_permission(article)
@ -276,6 +260,9 @@ class TicketArticlesController < ApplicationController
private private
def article_permission(article) def article_permission(article)
if current_user.permissions?('ticket.customer')
raise Exceptions::NotAuthorized if article.internal == true
end
ticket = Ticket.lookup(id: article.ticket_id) ticket = Ticket.lookup(id: article.ticket_id)
return true if ticket.permission(current_user: current_user) return true if ticket.permission(current_user: current_user)
raise Exceptions::NotAuthorized raise Exceptions::NotAuthorized

View file

@ -44,10 +44,7 @@ class Ticket::Article < ApplicationModel
insert inline image urls to body insert inline image urls to body
article_attributes = Ticket::Article.insert_urls( article_attributes = Ticket::Article.insert_urls(article_attributes)
article_attributes,
attachments,
)
returns returns
@ -55,23 +52,30 @@ returns
=end =end
def self.insert_urls(article, attachments) def self.insert_urls(article)
return article if article['attachments'].empty?
return article if article['content_type'] !~ %r{text/html}i
return article if article['body'] !~ /<img/i
inline_attachments = {} inline_attachments = {}
article['body'].gsub!( /(<img[[:space:]](.+?|)src=")cid:(.+?)(">)/i ) { |item| article['body'].gsub!( /(<img[[:space:]](|.+?)src=")cid:(.+?)"(|.+?)>/im ) { |item|
tag_start = $1
cid = $3
tag_end = $4
replace = item replace = item
# look for attachment # look for attachment
attachments.each { |file| article['attachments'].each { |file|
next if !file.preferences['Content-ID'] || file.preferences['Content-ID'] != $3 next if !file[:preferences] || !file[:preferences]['Content-ID'] || (file[:preferences]['Content-ID'] != cid && file[:preferences]['Content-ID'] != "<#{cid}>" )
replace = "#{$1}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file.id}#{$4}" replace = "#{tag_start}/api/v1/ticket_attachment/#{article['ticket_id']}/#{article['id']}/#{file[:id]}\"#{tag_end}>"
inline_attachments[file.id] = true inline_attachments[file[:id]] = true
break break
} }
replace replace
} }
new_attachments = [] new_attachments = []
attachments.each { |file| article['attachments'].each { |file|
next if inline_attachments[file.id] next if inline_attachments[file[:id]]
new_attachments.push file new_attachments.push file
} }
article['attachments'] = new_attachments article['attachments'] = new_attachments
@ -93,11 +97,12 @@ returns
def attachments_inline def attachments_inline
inline_attachments = {} inline_attachments = {}
body.gsub( /<img[[:space:]](.+?|)src="cid:(.+?)">/i ) { |_item| body.gsub( /<img[[:space:]](|.+?)src="cid:(.+?)"(|.+?)>/im ) { |_item|
cid = $2
# look for attachment # look for attachment
attachments.each { |file| attachments.each { |file|
next if !file.preferences['Content-ID'] || file.preferences['Content-ID'] != $2 next if !file.preferences['Content-ID'] || (file.preferences['Content-ID'] != cid && file.preferences['Content-ID'] != "<#{cid}>" )
inline_attachments[file.id] = true inline_attachments[file.id] = true
break break
} }
@ -212,6 +217,62 @@ returns:
content_type =~ /html/i content_type =~ /html/i
end end
=begin
get relation name of model based on params
model = Model.find(1)
attributes = model.attributes_with_association_names
returns
hash with attributes, association ids, association names and relation name
=end
def attributes_with_association_names
attributes = super
attributes['attachments'] = []
attachments.each { |attachment|
item = {
id: attachment['id'],
filename: attachment['filename'],
size: attachment['size'],
preferences: attachment['preferences'],
}
attributes['attachments'].push item
}
Ticket::Article.insert_urls(attributes)
end
=begin
get relations of model based on params
model = Model.find(1)
attributes = model.attributes_with_association_ids
returns
hash with attributes and association ids
=end
def attributes_with_association_ids
attributes = super
attributes['attachments'] = []
attachments.each { |attachment|
item = {
id: attachment['id'],
filename: attachment['filename'],
size: attachment['size'],
preferences: attachment['preferences'],
}
attributes['attachments'].push item
}
Ticket::Article.insert_urls(attributes)
end
private private
# strip not wanted chars # strip not wanted chars

View file

@ -39,24 +39,7 @@ returns
data[ app_model_article ] = {} data[ app_model_article ] = {}
end end
if !data[ app_model_article ][ id ] if !data[ app_model_article ][ id ]
data[ app_model_article ][ id ] = attributes data[ app_model_article ][ id ] = attributes_with_association_ids
# add attachment list to article
data[ app_model_article ][ id ]['attachments'] = attachments
if !data[ app_model_article ][ id ]['attachments'].empty?
if data[ app_model_article ][ id ]['content_type'] =~ %r{text/html}i
if data[ app_model_article ][ id ]['body'] =~ /<img/i
# insert inline images with urls
attributes = Ticket::Article.insert_urls(
data[ app_model_article ][ id ],
data[ app_model_article ][ id ]['attachments']
)
data[ app_model_article ][ id ] = attributes
end
end
end
end end
%w(created_by_id updated_by_id).each { |local_user_id| %w(created_by_id updated_by_id).each { |local_user_id|

View file

@ -0,0 +1,273 @@
# encoding: utf-8
require 'test_helper'
class TicketArticlesControllerTest < ActionDispatch::IntegrationTest
setup do
# set accept header
@headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
# create agent
roles = Role.where(name: %w(Admin Agent))
groups = Group.all
UserInfo.current_user_id = 1
@admin = User.create_or_update(
login: 'tickets-admin',
firstname: 'Tickets',
lastname: 'Admin',
email: 'tickets-admin@example.com',
password: 'adminpw',
active: true,
roles: roles,
groups: groups,
)
# create agent
roles = Role.where(name: 'Agent')
@agent = User.create_or_update(
login: 'tickets-agent@example.com',
firstname: 'Tickets',
lastname: 'Agent',
email: 'tickets-agent@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
)
# create customer without org
roles = Role.where(name: 'Customer')
@customer_without_org = User.create_or_update(
login: 'tickets-customer1@example.com',
firstname: 'Tickets',
lastname: 'Customer1',
email: 'tickets-customer1@example.com',
password: 'customer1pw',
active: true,
roles: roles,
)
end
test '01.01 ticket create with agent and articles' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #1',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
body: 'some body',
}
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
params = {
ticket_id: result['id'],
content_type: 'text/plain', # or text/html
body: 'some body',
type: 'note',
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
ticket = Ticket.find(result['ticket_id'])
assert_equal(2, ticket.articles.count)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
params = {
ticket_id: result['ticket_id'],
content_type: 'text/html', # or text/html
body: 'some body <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />',
type: 'note',
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_no_match(/some body <img src="cid:/, result['body'])
assert_match(%r{some body <img src="/api/v1/ticket_attachment/.}, result['body'])
assert_equal('text/html', result['content_type'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
assert_equal(3, ticket.articles.count)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
assert_equal(1, ticket.articles[2].attachments.count)
params = {
ticket_id: result['ticket_id'],
content_type: 'text/html', # or text/html
body: 'some body',
type: 'note',
attachments: [
'filename' => 'some_file.txt',
'data' => 'dGVzdCAxMjM=',
'mime-type' => 'text/plain',
],
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/html', result['content_type'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
assert_equal(4, ticket.articles.count)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
assert_equal(1, ticket.articles[2].attachments.count)
assert_equal(1, ticket.articles[3].attachments.count)
get "/api/v1/ticket_articles/#{result['id']}?expand=true", {}.to_json, @headers.merge('Authorization' => credentials)
assert_response(200)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(1, result['attachments'].count)
end
test '02.01 ticket create with customer and articles' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-customer1@example.com', 'customer1pw')
params = {
title: 'a new ticket #2',
group: 'Users',
article: {
body: 'some body',
}
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
params = {
ticket_id: result['id'],
content_type: 'text/plain', # or text/html
body: 'some body',
type: 'note',
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])
assert_equal(@customer_without_org.id, result['updated_by_id'])
assert_equal(@customer_without_org.id, result['created_by_id'])
ticket = Ticket.find(result['ticket_id'])
assert_equal(2, ticket.articles.count)
assert_equal('Customer', ticket.articles[1].sender.name)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
params = {
ticket_id: result['ticket_id'],
content_type: 'text/plain', # or text/html
body: 'some body',
sender: 'Agent',
type: 'note',
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_equal('some body', result['body'])
assert_equal('text/plain', result['content_type'])
assert_equal(@customer_without_org.id, result['updated_by_id'])
assert_equal(@customer_without_org.id, result['created_by_id'])
ticket = Ticket.find(result['ticket_id'])
assert_equal(3, ticket.articles.count)
assert_equal('Customer', ticket.articles[2].sender.name)
assert_equal(false, ticket.articles[2].internal)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
assert_equal(0, ticket.articles[2].attachments.count)
params = {
ticket_id: result['ticket_id'],
content_type: 'text/plain', # or text/html
body: 'some body 2',
sender: 'Agent',
type: 'note',
internal: true,
}
post '/api/v1/ticket_articles', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(nil, result['subject'])
assert_equal('some body 2', result['body'])
assert_equal('text/plain', result['content_type'])
assert_equal(@customer_without_org.id, result['updated_by_id'])
assert_equal(@customer_without_org.id, result['created_by_id'])
ticket = Ticket.find(result['ticket_id'])
assert_equal(4, ticket.articles.count)
assert_equal('Customer', ticket.articles[3].sender.name)
assert_equal(false, ticket.articles[3].internal)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
assert_equal(0, ticket.articles[2].attachments.count)
assert_equal(0, ticket.articles[3].attachments.count)
# add internal article
article = Ticket::Article.create(
ticket_id: ticket.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
message_id: 'some@id',
body: 'some message 123',
internal: true,
sender: Ticket::Article::Sender.find_by(name: 'Agent'),
type: Ticket::Article::Type.find_by(name: 'note'),
updated_by_id: 1,
created_by_id: 1,
)
assert_equal(5, ticket.articles.count)
assert_equal('Agent', ticket.articles[4].sender.name)
assert_equal(1, ticket.articles[4].updated_by_id)
assert_equal(1, ticket.articles[4].created_by_id)
assert_equal(0, ticket.articles[0].attachments.count)
assert_equal(0, ticket.articles[1].attachments.count)
assert_equal(0, ticket.articles[2].attachments.count)
assert_equal(0, ticket.articles[3].attachments.count)
assert_equal(0, ticket.articles[4].attachments.count)
get "/api/v1/ticket_articles/#{article.id}", {}.to_json, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('Not authorized', result['error'])
put "/api/v1/ticket_articles/#{article.id}", { internal: false }.to_json, @headers.merge('Authorization' => credentials)
assert_response(401)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('Not authorized', result['error'])
end
end

View file

@ -263,6 +263,231 @@ class TicketsControllerTest < ActionDispatch::IntegrationTest
assert_equal(@agent.id, result['created_by_id']) assert_equal(@agent.id, result['created_by_id'])
end end
test '01.10 ticket create with agent - minimal article with missing body - with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #10',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
subject: 'some test 123',
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(422)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('Need at least article: { body: "some text" }', result['error'])
end
test '01.11 ticket create with agent - minimal article and attachment with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #11',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
subject: 'some test 123',
body: 'some test 123',
attachments: [
'filename' => 'some_file.txt',
'data' => 'dGVzdCAxMjM=',
'mime-type' => 'text/plain',
],
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(Ticket::State.lookup(name: 'new').id, result['state_id'])
assert_equal('a new ticket #11', result['title'])
assert_equal(@customer_without_org.id, result['customer_id'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
ticket = Ticket.find(result['id'])
assert_equal(1, ticket.articles.count)
assert_equal(1, ticket.articles.first.attachments.count)
file = ticket.articles.first.attachments.first
assert_equal('test 123', file.content)
assert_equal('some_file.txt', file.filename)
assert_equal('text/plain', file.preferences['Mime-Type'])
assert_not(file.preferences['Content-ID'])
end
test '01.12 ticket create with agent - minimal article and attachment with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #12',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
subject: 'some test 123',
body: 'some test 123',
attachments: [
{
'filename' => 'some_file1.txt',
'data' => 'dGVzdCAxMjM=',
'mime-type' => 'text/plain',
},
{
'filename' => 'some_file2.txt',
'data' => 'w6TDtsO8w58=',
'mime-type' => 'text/plain',
},
],
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(Ticket::State.lookup(name: 'new').id, result['state_id'])
assert_equal('a new ticket #12', result['title'])
assert_equal(@customer_without_org.id, result['customer_id'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
ticket = Ticket.find(result['id'])
assert_equal(1, ticket.articles.count)
assert_equal(2, ticket.articles.first.attachments.count)
file = ticket.articles.first.attachments.first
assert_equal('test 123', file.content)
assert_equal('some_file1.txt', file.filename)
assert_equal('text/plain', file.preferences['Mime-Type'])
assert_not(file.preferences['Content-ID'])
end
test '01.13 ticket create with agent - minimal article and attachment missing mine-type with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #13',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
subject: 'some test 123',
body: 'some test 123',
attachments: [
'filename' => 'some_file.txt',
'data' => 'ABC_INVALID_BASE64',
'mime-type' => 'text/plain',
],
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(422)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('Invalid base64 for attachment with index \'0\'', result['error'])
end
test '01.14 ticket create with agent - minimal article and attachment invalid base64 with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #14',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
subject: 'some test 123',
body: 'some test 123',
attachments: [
'filename' => 'some_file.txt',
'data' => 'dGVzdCAxMjM=',
],
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(422)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal('Attachment needs \'mime-type\' param for attachment with index \'0\'', result['error'])
end
test '01.15 ticket create with agent - minimal article and inline attachments with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #15',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
content_type: 'text/html',
subject: 'some test 123',
body: 'some test 123 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" /> <img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAJAAD/4QMtaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzJCOTE2NzlGQUEwMTFFNjg0M0NGQjU0OUU4MTFEOEIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzJCOTE2N0FGQUEwMTFFNjg0M0NGQjU0OUU4MTFEOEIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMkI5MTY3N0ZBQTAxMUU2ODQzQ0ZCNTQ5RTgxMUQ4QiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMkI5MTY3OEZBQTAxMUU2ODQzQ0ZCNTQ5RTgxMUQ4QiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABQRERoTGioZGSo1KCEoNTEpKCgpMUE4ODg4OEFEREREREREREREREREREREREREREREREREREREREREREREREQBFhoaIh0iKRoaKTkpIik5RDktLTlEREREOERERERERERERERERERERERERERERERERERERERERERERERERERERP/AABEIABAADAMBIgACEQEDEQH/xABbAAEBAAAAAAAAAAAAAAAAAAAEBQEBAQAAAAAAAAAAAAAAAAAABAUQAAEEAgMAAAAAAAAAAAAAAAABAhIDESIxBAURAAICAwAAAAAAAAAAAAAAAAESABNRoQP/2gAMAwEAAhEDEQA/AJDq1rfF3Imeg/1+lFy2oR564DKWWWbweV+Buf/Z">',
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(Ticket::State.lookup(name: 'new').id, result['state_id'])
assert_equal('a new ticket #15', result['title'])
assert_equal(@customer_without_org.id, result['customer_id'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
ticket = Ticket.find(result['id'])
assert_equal(1, ticket.articles.count)
assert_equal(2, ticket.articles.first.attachments.count)
file = ticket.articles.first.attachments[0]
assert_equal('d3c1e09bdefb92b6a06b791a24ca9599', Digest::MD5.hexdigest(file.content))
assert_match(/#{ticket.id}\..+?@zammad.example.com/, file.filename)
assert_equal('image/png', file.preferences['Mime-Type'])
assert(file.preferences['Content-ID'])
file = ticket.articles.first.attachments[1]
assert_equal('006a2ca3793b550c8fe444acdeb39252', Digest::MD5.hexdigest(file.content))
assert_match(/#{ticket.id}\..+?@zammad.example.com/, file.filename)
assert_equal('image/jpeg', file.preferences['Mime-Type'])
assert(file.preferences['Content-ID'])
end
test '01.16 ticket create with agent - minimal article and inline attachments with customer' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = {
title: 'a new ticket #16',
group: 'Users',
customer_id: @customer_without_org.id,
article: {
content_type: 'text/html',
subject: 'some test 123',
body: 'some test 123 <img src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAJAAD/4QMtaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzJCOTE2NzlGQUEwMTFFNjg0M0NGQjU0OUU4MTFEOEIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzJCOTE2N0FGQUEwMTFFNjg0M0NGQjU0OUU4MTFEOEIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMkI5MTY3N0ZBQTAxMUU2ODQzQ0ZCNTQ5RTgxMUQ4QiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDMkI5MTY3OEZBQTAxMUU2ODQzQ0ZCNTQ5RTgxMUQ4QiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEABQRERoTGioZGSo1KCEoNTEpKCgpMUE4ODg4OEFEREREREREREREREREREREREREREREREREREREREREREREREQBFhoaIh0iKRoaKTkpIik5RDktLTlEREREOERERERERERERERERERERERERERERERERERERERERERERERERERERP/AABEIABAADAMBIgACEQEDEQH/xABbAAEBAAAAAAAAAAAAAAAAAAAEBQEBAQAAAAAAAAAAAAAAAAAABAUQAAEEAgMAAAAAAAAAAAAAAAABAhIDESIxBAURAAICAwAAAAAAAAAAAAAAAAESABNRoQP/2gAMAwEAAhEDEQA/AJDq1rfF3Imeg/1+lFy2oR564DKWWWbweV+Buf/Z"
>',
attachments: [
'filename' => 'some_file.txt',
'data' => 'dGVzdCAxMjM=',
'mime-type' => 'text/plain',
],
},
}
post '/api/v1/tickets', params.to_json, @headers.merge('Authorization' => credentials)
assert_response(201)
result = JSON.parse(@response.body)
assert_equal(Hash, result.class)
assert_equal(Ticket::State.lookup(name: 'new').id, result['state_id'])
assert_equal('a new ticket #16', result['title'])
assert_equal(@customer_without_org.id, result['customer_id'])
assert_equal(@agent.id, result['updated_by_id'])
assert_equal(@agent.id, result['created_by_id'])
ticket = Ticket.find(result['id'])
assert_equal(1, ticket.articles.count)
assert_equal(2, ticket.articles.first.attachments.count)
file = ticket.articles.first.attachments[0]
assert_equal('006a2ca3793b550c8fe444acdeb39252', Digest::MD5.hexdigest(file.content))
assert_match(/#{ticket.id}\..+?@zammad.example.com/, file.filename)
assert_equal('image/jpeg', file.preferences['Mime-Type'])
assert(file.preferences['Content-ID'])
file = ticket.articles.first.attachments[1]
assert_equal('39d0d586a701e199389d954f2d592720', Digest::MD5.hexdigest(file.content))
assert_equal('some_file.txt', file.filename)
assert_equal('text/plain', file.preferences['Mime-Type'])
assert_not(file.preferences['Content-ID'])
end
test '02.02 ticket create with agent' do test '02.02 ticket create with agent' do
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw') credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-agent@example.com', 'agentpw')
params = { params = {

View file

@ -307,7 +307,7 @@ class TicketTest < ActiveSupport::TestCase
end end
test 'article attachment helper' do test 'article attachment helper 1' do
ticket1 = Ticket.create( ticket1 = Ticket.create(
title: 'some article helper test1', title: 'some article helper test1',
@ -376,10 +376,7 @@ class TicketTest < ActiveSupport::TestCase
created_by_id: 1, created_by_id: 1,
) )
article_attributes = Ticket::Article.insert_urls( article_attributes = Ticket::Article.insert_urls(article1.attributes_with_association_ids)
article1.attributes,
article1.attachments,
)
assert_no_match('15.274327094.140938@zammad.example.com', article_attributes['body']) assert_no_match('15.274327094.140938@zammad.example.com', article_attributes['body'])
assert_no_match('15.274327094.140939@zammad.example.com', article_attributes['body']) assert_no_match('15.274327094.140939@zammad.example.com', article_attributes['body'])
@ -394,6 +391,92 @@ class TicketTest < ActiveSupport::TestCase
assert_equal(store1.id, attachments.first.id) assert_equal(store1.id, attachments.first.id)
ticket1.destroy ticket1.destroy
end
test 'article attachment helper 2' do
ticket1 = Ticket.create(
title: 'some article helper test2',
group: Group.lookup(name: 'Users'),
customer_id: 2,
state: Ticket::State.lookup(name: 'new'),
priority: Ticket::Priority.lookup(name: '2 normal'),
updated_by_id: 1,
created_by_id: 1,
)
assert(ticket1, 'ticket created')
# create inbound article #1
article1 = Ticket::Article.create(
ticket_id: ticket1.id,
from: 'some_sender@example.com',
to: 'some_recipient@example.com',
subject: 'some subject',
message_id: 'some@id',
content_type: 'text/html',
body: 'some message article helper test2 <div><img src="cid:15.274327094.140938@zammad.example.com">asdasd<img border="0" width="60" height="19" src="cid:15.274327094.140939@zammad.example.com" alt="Beschreibung: Beschreibung: efqmLogo"><br>',
internal: false,
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
type: Ticket::Article::Type.find_by(name: 'email'),
updated_by_id: 1,
created_by_id: 1,
)
store1 = Store.add(
object: 'Ticket::Article',
o_id: article1.id,
data: 'content_file1_normally_should_be_an_image',
filename: 'some_file1.jpg',
preferences: {
'Content-Type' => 'image/jpeg',
'Mime-Type' => 'image/jpeg',
'Content-ID' => '15.274327094.140938@zammad.example.com',
'Content-Disposition' => 'inline'
},
created_by_id: 1,
)
store2 = Store.add(
object: 'Ticket::Article',
o_id: article1.id,
data: 'content_file2_normally_should_be_an_image',
filename: 'some_file2.jpg',
preferences: {
'Content-Type' => 'image/jpeg',
'Mime-Type' => 'image/jpeg',
'Content-ID' => '15.274327094.140939@zammad.example.com',
'Content-Disposition' => 'inline'
},
created_by_id: 1,
)
store3 = Store.add(
object: 'Ticket::Article',
o_id: article1.id,
data: 'content_file3',
filename: 'some_file3.txt',
preferences: {
'Content-Type' => 'text/stream',
'Mime-Type' => 'text/stream',
'Content-ID' => '15.274327094.99999@zammad.example.com',
'Content-Disposition' => 'inline'
},
created_by_id: 1,
)
article_attributes = Ticket::Article.insert_urls(article1.attributes_with_association_ids)
assert_no_match('15.274327094.140938@zammad.example.com', article_attributes['body'])
assert_no_match('15.274327094.140939@zammad.example.com', article_attributes['body'])
assert_no_match('15.274327094.99999@zammad.example.com', article_attributes['body'])
assert_match("api/v1/ticket_attachment/#{ticket1.id}/#{article1.id}/#{store1.id}", article_attributes['body'])
assert_match("api/v1/ticket_attachment/#{ticket1.id}/#{article1.id}/#{store2.id}", article_attributes['body'])
assert_no_match("api/v1/ticket_attachment/#{ticket1.id}/#{article1.id}/#{store3.id}", article_attributes['body'])
article1 = Ticket::Article.find(article1.id)
attachments = article1.attachments_inline
assert_equal(2, attachments.length)
assert_equal(store1.id, attachments.first.id)
ticket1.destroy
end
end end
end