diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb index 1372d82c4..823109586 100644 --- a/app/controllers/organizations_controller.rb +++ b/app/controllers/organizations_controller.rb @@ -323,4 +323,43 @@ curl http://localhost/api/v1/organization/{id} -v -u #{login}:#{password} -H "Co render json: history end + # @path [GET] /organizations/import_example + # + # @summary Download of example CSV file. + # @notes The requester have 'admin.organization' permissions to be able to download it. + # @example curl -u 'me@example.com:test' http://localhost:3000/api/v1/organizations/import_example + # + # @response_message 200 File download. + # @response_message 401 Invalid session. + def import_example + permission_check('admin.organization') + send_data( + Organization.csv_example, + filename: 'organization-example.csv', + type: 'text/csv', + disposition: 'attachment' + ) + end + + # @path [POST] /organizations/import + # + # @summary Starts import. + # @notes The requester have 'admin.text_module' permissions to be create a new import. + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/organizations.csv' 'https://your.zammad/api/v1/organizations/import?try=true' + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/organizations.csv' 'https://your.zammad/api/v1/organizations/import' + # + # @response_message 201 Import started. + # @response_message 401 Invalid session. + def import_start + permission_check('admin.user') + result = Organization.csv_import( + string: params[:file].read.force_encoding('utf-8'), + parse_params: { + col_sep: ';', + }, + try: params[:try], + ) + render json: result, status: :ok + end + end diff --git a/app/controllers/text_modules_controller.rb b/app/controllers/text_modules_controller.rb index dada3340c..cd3f414e0 100644 --- a/app/controllers/text_modules_controller.rb +++ b/app/controllers/text_modules_controller.rb @@ -152,4 +152,48 @@ curl http://localhost/api/v1/text_modules.json -v -u #{login}:#{password} -H "Co permission_check('admin.text_module') model_destroy_render(TextModule, params) end + + # @path [GET] /text_modules/import_example + # + # @summary Download of example CSV file. + # @notes The requester have 'admin.text_module' permissions to be able to download it. + # @example curl -u 'me@example.com:test' http://localhost:3000/api/v1/text_modules/import_example + # + # @response_message 200 File download. + # @response_message 401 Invalid session. + def import_example + permission_check('admin.text_module') + csv_string = TextModule.csv_example( + col_sep: ',', + ) + send_data( + csv_string, + filename: 'example.csv', + type: 'text/csv', + disposition: 'attachment' + ) + + end + + # @path [POST] /text_modules/import + # + # @summary Starts import. + # @notes The requester have 'admin.text_module' permissions to be create a new import. + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/Textbausteine_final2.csv' 'https://your.zammad/api/v1/text_modules/import?try=true' + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/Textbausteine_final2.csv' 'https://your.zammad/api/v1/text_modules/import' + # + # @response_message 201 Import started. + # @response_message 401 Invalid session. + def import_start + permission_check('admin.text_module') + result = TextModule.csv_import( + string: params[:file].read.force_encoding('utf-8'), + parse_params: { + col_sep: ';', + }, + try: params[:try], + ) + render json: result, status: :ok + end + end diff --git a/app/controllers/ticket_articles_controller.rb b/app/controllers/ticket_articles_controller.rb index 35f845a0e..2e1084023 100644 --- a/app/controllers/ticket_articles_controller.rb +++ b/app/controllers/ticket_articles_controller.rb @@ -276,6 +276,52 @@ class TicketArticlesController < ApplicationController ) end + # @path [GET] /ticket_articles/import_example + # + # @summary Download of example CSV file. + # @notes The requester have 'admin' permissions to be able to download it. + # @example curl -u 'me@example.com:test' http://localhost:3000/api/v1/ticket_articles/import_example + # + # @response_message 200 File download. + # @response_message 401 Invalid session. + def import_example + permission_check('admin') + csv_string = Ticket::Article.csv_example( + col_sep: ',', + ) + send_data( + csv_string, + filename: 'example.csv', + type: 'text/csv', + disposition: 'attachment' + ) + + end + + # @path [POST] /ticket_articles/import + # + # @summary Starts import. + # @notes The requester have 'admin' permissions to be create a new import. + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/ticket_articles.csv' 'https://your.zammad/api/v1/ticket_articles/import?try=true' + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/ticket_articles.csv' 'https://your.zammad/api/v1/ticket_articles/import' + # + # @response_message 201 Import started. + # @response_message 401 Invalid session. + def import_start + permission_check('admin') + if Setting.get('import_mode') != true + raise 'Only can import tickets if system is in import mode.' + end + result = Ticket::Article.csv_import( + string: params[:file].read.force_encoding('utf-8'), + parse_params: { + col_sep: ';', + }, + try: params[:try], + ) + render json: result, status: :ok + end + private def sanitized_disposition diff --git a/app/controllers/tickets_controller.rb b/app/controllers/tickets_controller.rb index dbdf36ef6..6c326b5f0 100644 --- a/app/controllers/tickets_controller.rb +++ b/app/controllers/tickets_controller.rb @@ -598,6 +598,52 @@ class TicketsController < ApplicationController } end + # @path [GET] /tickets/import_example + # + # @summary Download of example CSV file. + # @notes The requester have 'admin' permissions to be able to download it. + # @example curl -u 'me@example.com:test' http://localhost:3000/api/v1/tickets/import_example + # + # @response_message 200 File download. + # @response_message 401 Invalid session. + def import_example + permission_check('admin') + csv_string = Ticket.csv_example( + col_sep: ',', + ) + send_data( + csv_string, + filename: 'example.csv', + type: 'text/csv', + disposition: 'attachment' + ) + + end + + # @path [POST] /tickets/import + # + # @summary Starts import. + # @notes The requester have 'admin' permissions to be create a new import. + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/tickets.csv' 'https://your.zammad/api/v1/tickets/import?try=true' + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/tickets.csv' 'https://your.zammad/api/v1/tickets/import' + # + # @response_message 201 Import started. + # @response_message 401 Invalid session. + def import_start + permission_check('admin') + if Setting.get('import_mode') != true + raise 'Only can import tickets if system is in import mode.' + end + result = Ticket.csv_import( + string: params[:file].read.force_encoding('utf-8'), + parse_params: { + col_sep: ';', + }, + try: params[:try], + ) + render json: result, status: :ok + end + private def follow_up_possible_check diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index dc20b18ef..f3c982d3e 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1061,6 +1061,45 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content render json: { avatars: result }, status: :ok end + # @path [GET] /users/import_example + # + # @summary Download of example CSV file. + # @notes The requester have 'admin.user' permissions to be able to download it. + # @example curl -u 'me@example.com:test' http://localhost:3000/api/v1/users/import_example + # + # @response_message 200 File download. + # @response_message 401 Invalid session. + def import_example + permission_check('admin.user') + send_data( + User.csv_example, + filename: 'user-example.csv', + type: 'text/csv', + disposition: 'attachment' + ) + end + + # @path [POST] /users/import + # + # @summary Starts import. + # @notes The requester have 'admin.text_module' permissions to be create a new import. + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/users.csv' 'https://your.zammad/api/v1/users/import?try=true' + # @example curl -u 'me@example.com:test' -F 'file=@/path/to/file/users.csv' 'https://your.zammad/api/v1/users/import' + # + # @response_message 201 Import started. + # @response_message 401 Invalid session. + def import_start + permission_check('admin.user') + result = User.csv_import( + string: params[:file].read.force_encoding('utf-8'), + parse_params: { + col_sep: ';', + }, + try: params[:try], + ) + render json: result, status: :ok + end + private def password_policy(password) diff --git a/app/models/application_model/can_associations.rb b/app/models/application_model/can_associations.rb index 2a625f19e..979054c5a 100644 --- a/app/models/application_model/can_associations.rb +++ b/app/models/application_model/can_associations.rb @@ -63,6 +63,9 @@ returns real_values = real_values.to_sym next if !respond_to?(real_values) next if !params[real_values] + if params[real_values].instance_of?(String) || params[real_values].instance_of?(Integer) || params[real_values].instance_of?(Float) + params[real_values] = [params[real_values]] + end next if !params[real_values].instance_of?(Array) list = [] class_object = assoc.klass diff --git a/app/models/application_model/can_lookup.rb b/app/models/application_model/can_lookup.rb index b5f8e1ae4..61bd21898 100644 --- a/app/models/application_model/can_lookup.rb +++ b/app/models/application_model/can_lookup.rb @@ -39,10 +39,9 @@ returns where(name: data[:name]) end records.each do |loop_record| - if loop_record.name == data[:name] - cache_set(data[:name], loop_record) - return loop_record - end + next if loop_record.name != data[:name] + cache_set(data[:name], loop_record) + return loop_record end return elsif data[:login] @@ -56,10 +55,9 @@ returns where(login: data[:login]) end records.each do |loop_record| - if loop_record.login == data[:login] - cache_set(data[:login], loop_record) - return loop_record - end + next if loop_record.login != data[:login] + cache_set(data[:login], loop_record) + return loop_record end return elsif data[:email] @@ -73,15 +71,27 @@ returns where(email: data[:email]) end records.each do |loop_record| - if loop_record.email == data[:email] - cache_set(data[:email], loop_record) - return loop_record - end + next if loop_record.email != data[:email] + cache_set(data[:email], loop_record) + return loop_record + end + return + elsif data[:number] + + # do lookup with == to handle case insensitive databases + records = if Rails.application.config.db_case_sensitive + where('LOWER(number) = LOWER(?)', data[:number]) + else + where(number: data[:number]) + end + records.each do |loop_record| + next if loop_record.number != data[:number] + return loop_record end return end - raise ArgumentError, 'Need name, id, login or email for lookup()' + raise ArgumentError, 'Need name, id, number, login or email for lookup()' end end end diff --git a/app/models/concerns/can_csv_import.rb b/app/models/concerns/can_csv_import.rb new file mode 100644 index 000000000..ea8e49c80 --- /dev/null +++ b/app/models/concerns/can_csv_import.rb @@ -0,0 +1,351 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +require 'csv' + +module CanCsvImport + extend ActiveSupport::Concern + + # methods defined here are going to extend the class, not the instance of it + class_methods do + +=begin + + result = Model.csv_import( + string: csv_string, + parse_params: { + col_sep: ',', + }, + try: true, + ) + + result = Model.csv_import( + file: '/file/location/of/file.csv', + parse_params: { + col_sep: ',', + }, + try: true, + ) + + result = TextModule.csv_import( + file: '/Users/me/Downloads/Textbausteine_final.csv', + parse_params: { + col_sep: ',', + }, + try: false, + ) + +returns + + { + records: [record1, ...] + try: true, # true|false + success: true, # true|false + } + +=end + + def csv_import(data) + + if data[:file].present? + raise Exceptions::UnprocessableEntity, "No such file '#{data[:file]}'" if !File.exist?(data[:file]) + begin + file = File.open(data[:file], 'r:UTF-8') + data[:string] = file.read + rescue => e + raise Exceptions::UnprocessableEntity, "Unable to read file '#{data[:file]}': #{e.inspect}" + end + end + if data[:string].blank? + raise Exceptions::UnprocessableEntity, 'Unable to parse empty file/string!' + end + + rows = CSV.parse(data[:string], data[:parse_params]) + header = rows.shift + if header.blank? + raise Exceptions::UnprocessableEntity, 'Unable to parse file/string without header!' + end + header.each do |item| + if item.respond_to?(:strip!) + item.strip! + end + next if !item.respond_to?(:downcase!) + item.downcase! + end + + # get payload based on csv + payload = [] + rows.each do |row| + if row[0].blank? && row[1].blank? + payload_last = payload.last + row.each_with_index do |item, count| + next if item.blank? + if payload_last[header[count].to_sym].class != Array + payload_last[header[count].to_sym] = [payload_last[header[count].to_sym]] + end + payload_last[header[count].to_sym].push item.strip + end + next + end + attributes = {} + row.each_with_index do |item, count| + next if !item + next if header[count].blank? + next if @csv_attributes_ignored&.include?(header[count].to_sym) + attributes[header[count].to_sym] = if item.respond_to?(:strip) + item.strip + else + item + end + end + data[:fixed_params]&.each do |key, value| + attributes[key] = value + end + payload.push attributes + end + + # create or update records + csv_object_ids_ignored = @csv_object_ids_ignored || [] + records = [] + stats = { + created: 0, + updated: 0, + } + errors = [] + line_count = 0 + payload.each do |attributes| + line_count += 1 + record = nil + %i[id number name login email].each do |lookup_by| + next if !attributes[lookup_by] + params = {} + params[lookup_by] = attributes[lookup_by] + record = lookup(params) + break if record + end + + if attributes[:id].present? && !record + errors.push "Line #{line_count}: unknown record with id '#{attributes[:id]}' for #{new.class}." + next + end + + if record && csv_object_ids_ignored.include?(record.id) + errors.push "Line #{line_count}: unable to update record with id '#{attributes[:id]}' for #{new.class}." + next + end + + begin + clean_params = association_name_to_id_convert(attributes) + rescue => e + errors.push "Line #{line_count}: #{e.message}" + next + end + + # create object + Transaction.execute(disable_notification: true, reset_user_id: true) do + UserInfo.current_user_id = clean_params[:updated_by_id] || clean_params[:created_by_id] + if !record + stats[:created] += 1 + begin + csv_verify_attributes(clean_params) + clean_params = param_cleanup(clean_params) + + if !UserInfo.current_user_id + clean_params[:created_by_id] = 1 + clean_params[:updated_by_id] = 1 + end + record = new(clean_params) + next if data[:try] == 'true' || data[:try] == true + record.associations_from_param(attributes) + record.save! + rescue => e + errors.push "Line #{line_count}: #{e.message}" + next + end + else + stats[:updated] += 1 + next if data[:try] == 'true' || data[:try] == true + begin + csv_verify_attributes(clean_params) + clean_params = param_cleanup(clean_params) + + if !UserInfo.current_user_id + clean_params[:updated_by_id] = 1 + end + + record.with_lock do + record.associations_from_param(attributes) + record.update_attributes!(clean_params) + end + rescue => e + errors.push "Line #{line_count}: #{e.message}" + next + end + end + end + + records.push record + end + + result = 'success' + if errors.present? + result = 'failed' + end + + { + stats: stats, + records: records, + errors: errors, + try: data[:try], + result: result, + } + + end + +=begin + +verify if attributes are valid, will raise an ArgumentError with "unknown attribute '#{key}' for #{new.class}." + + Model.csv_verify_attributes({'attribute': 'some value'}) + +=end + + def csv_verify_attributes(clean_params) + all_clean_attributes = {} + new.attributes.each_key do |attribute| + all_clean_attributes[attribute.to_sym] = true + end + reflect_on_all_associations.map do |assoc| + all_clean_attributes[assoc.name.to_sym] = true + ref = if assoc.name.to_s.end_with?('_id') + "#{assoc.name}_id" + else + "#{assoc.name.to_s.chop}_ids" + end + all_clean_attributes[ref.to_sym] = true + end + clean_params.each_key do |key| + next if all_clean_attributes.key?(key.to_sym) + raise ArgumentError, "unknown attribute '#{key}' for #{new.class}." + end + true + end + +=begin + + csv_string = Model.csv_example( + col_sep: ',', + ) + +returns + + csv_string + +=end + + def csv_example(params = {}) + header = [] + csv_object_ids_ignored = @csv_object_ids_ignored || [] + records = where.not(id: csv_object_ids_ignored).offset(1).limit(23).to_a + if records.count < 20 + record_ids = records.pluck(:id).concat(csv_object_ids_ignored) + local_records = where.not(id: record_ids).limit(20 - records.count) + records = records.concat(local_records) + end + records_attributes_with_association_names = [] + records.each do |record| + record_attributes_with_association_names = record.attributes_with_association_names + records_attributes_with_association_names.push record_attributes_with_association_names + record_attributes_with_association_names.each do |key, value| + next if value.class == ActiveSupport::HashWithIndifferentAccess + next if value.class == Hash + next if @csv_attributes_ignored&.include?(key.to_sym) + next if key.match?(/_id$/) + next if key.match?(/_ids$/) + next if key == 'created_by' + next if key == 'updated_by' + next if key == 'created_at' + next if key == 'updated_at' + next if header.include?(key) + header.push key + end + end + + rows = [] + records_attributes_with_association_names.each do |record| + row = [] + rows_to_add = [] + position = -1 + header.each do |key| + position += 1 + if record[key].class == ActiveSupport::TimeWithZone + row.push record[key].iso8601 + next + end + if record[key].class == Array + entry_count = -2 + record[key].each do |entry| + entry_count += 1 + next if entry_count == -1 + if !rows_to_add[entry_count] + rows_to_add[entry_count] = Array.new(header.count + 1) { '' } + end + rows_to_add[entry_count][position] = entry + end + record[key] = record[key][0] + end + row.push record[key] + end + rows.push row + next unless rows_to_add.count.positive? + rows_to_add.each do |item| + rows.push item + end + rows_to_add = [] + end + CSV.generate(params) do |csv| + csv << header + rows.each do |row| + csv << row + end + end + end + +=begin + +serve methode to ignore model based on id + +class Model < ApplicationModel + include CanCsvImport + csv_object_ids_ignored(1, 2, 3) +end + +=end + + def csv_object_ids_ignored(*object_ids) + @csv_object_ids_ignored = object_ids + end + +=begin + +serve methode to ignore model attributes + +class Model < ApplicationModel + include CanCsvImport + csv_attributes_ignored :password, + :image_source, + :login_failed, + :source, + :image_source, + :image, + :authorizations, + :organizations + +end + +=end + + def csv_attributes_ignored(*attributes) + @csv_attributes_ignored = attributes + end + + end +end diff --git a/app/models/organization.rb b/app/models/organization.rb index 967e72558..93833ec63 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -6,6 +6,7 @@ class Organization < ApplicationModel include ChecksLatestChangeObserved include HasHistory include HasSearchIndexBackend + include CanCsvImport include Organization::ChecksAccess load 'organization/assets.rb' diff --git a/app/models/text_module.rb b/app/models/text_module.rb index 1cec59992..2b987c7d7 100644 --- a/app/models/text_module.rb +++ b/app/models/text_module.rb @@ -3,10 +3,14 @@ class TextModule < ApplicationModel include ChecksClientNotification include ChecksHtmlSanitized + include CanCsvImport validates :name, presence: true validates :content, presence: true + before_create :validate_content + before_update :validate_content + sanitized_html :content =begin @@ -97,4 +101,14 @@ push text_modules to online true end + private + + def validate_content + return true if content.blank? + return true if content.match?(/<.+?>/) + content.gsub!(/(\r\n|\n\r|\r)/, "\n") + self.content = content.text2html + true + end + end diff --git a/app/models/ticket.rb b/app/models/ticket.rb index b1a40c442..7007880cb 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -4,6 +4,7 @@ class Ticket < ApplicationModel include HasActivityStreamLog include ChecksClientNotification include ChecksLatestChangeObserved + include CanCsvImport include HasHistory include HasTags include HasSearchIndexBackend diff --git a/app/models/ticket/article.rb b/app/models/ticket/article.rb index 1067a3793..187472085 100644 --- a/app/models/ticket/article.rb +++ b/app/models/ticket/article.rb @@ -4,6 +4,7 @@ class Ticket::Article < ApplicationModel include ChecksClientNotification include HasHistory include ChecksHtmlSanitized + include CanCsvImport include Ticket::Article::ChecksAccess load 'ticket/article/assets.rb' diff --git a/app/models/user.rb b/app/models/user.rb index d7a6271a3..c3f6e0d7c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -28,6 +28,7 @@ class User < ApplicationModel include ChecksClientNotification include HasHistory include HasSearchIndexBackend + include CanCsvImport include HasGroups include HasRoles include User::ChecksAccess @@ -74,6 +75,18 @@ class User < ApplicationModel :source, :login_failed + csv_object_ids_ignored 1 + + csv_attributes_ignored :password, + :login_failed, + :source, + :image_source, + :image, + :authorizations, + :organizations, + :groups, + :user_groups + def ignore_search_indexing?(_action) # ignore internal user return true if id == 1 diff --git a/config/routes/organization.rb b/config/routes/organization.rb index 930a6c393..b1995c5cc 100644 --- a/config/routes/organization.rb +++ b/config/routes/organization.rb @@ -2,12 +2,14 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path # organizations - match api_path + '/organizations/search', to: 'organizations#search', via: %i[get post] - match api_path + '/organizations', to: 'organizations#index', via: :get - match api_path + '/organizations/:id', to: 'organizations#show', via: :get - match api_path + '/organizations', to: 'organizations#create', via: :post - match api_path + '/organizations/:id', to: 'organizations#update', via: :put - match api_path + '/organizations/:id', to: 'organizations#destroy', via: :delete - match api_path + '/organizations/history/:id', to: 'organizations#history', via: :get + match api_path + '/organizations/import_example', to: 'organizations#import_example', via: :get + match api_path + '/organizations/import', to: 'organizations#import_start', via: :post + match api_path + '/organizations/search', to: 'organizations#search', via: %i[get post] + match api_path + '/organizations', to: 'organizations#index', via: :get + match api_path + '/organizations/:id', to: 'organizations#show', via: :get + match api_path + '/organizations', to: 'organizations#create', via: :post + match api_path + '/organizations/:id', to: 'organizations#update', via: :put + match api_path + '/organizations/:id', to: 'organizations#destroy', via: :delete + match api_path + '/organizations/history/:id', to: 'organizations#history', via: :get end diff --git a/config/routes/text_module.rb b/config/routes/text_module.rb index 821b985dd..f1813feaf 100644 --- a/config/routes/text_module.rb +++ b/config/routes/text_module.rb @@ -2,10 +2,12 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path # text_modules - match api_path + '/text_modules', to: 'text_modules#index', via: :get - match api_path + '/text_modules/:id', to: 'text_modules#show', via: :get - match api_path + '/text_modules', to: 'text_modules#create', via: :post - match api_path + '/text_modules/:id', to: 'text_modules#update', via: :put - match api_path + '/text_modules/:id', to: 'text_modules#destroy', via: :delete + match api_path + '/text_modules/import_example', to: 'text_modules#import_example', via: :get + match api_path + '/text_modules/import', to: 'text_modules#import_start', via: :post + match api_path + '/text_modules', to: 'text_modules#index', via: :get + match api_path + '/text_modules/:id', to: 'text_modules#show', via: :get + match api_path + '/text_modules', to: 'text_modules#create', via: :post + match api_path + '/text_modules/:id', to: 'text_modules#update', via: :put + match api_path + '/text_modules/:id', to: 'text_modules#destroy', via: :delete end diff --git a/config/routes/user.rb b/config/routes/user.rb index 7847d30e3..c453d4e15 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -2,7 +2,7 @@ Zammad::Application.routes.draw do api_path = Rails.configuration.api_path # users - match api_path + '/users/search', to: 'users#search', via: %i[get post] + match api_path + '/users/search', to: 'users#search', via: %i[get post option] match api_path + '/users/recent', to: 'users#recent', via: %i[get post] match api_path + '/users/password_reset', to: 'users#password_reset_send', via: :post match api_path + '/users/password_reset_verify', to: 'users#password_reset_verify', via: :post @@ -11,6 +11,9 @@ Zammad::Application.routes.draw do match api_path + '/users/out_of_office', to: 'users#out_of_office', via: :put match api_path + '/users/account', to: 'users#account_remove', via: :delete + match api_path + '/users/import_example', to: 'users#import_example', via: :get + match api_path + '/users/import', to: 'users#import_start', via: :post + match api_path + '/users/avatar', to: 'users#avatar_new', via: :post match api_path + '/users/avatar', to: 'users#avatar_list', via: :get match api_path + '/users/avatar', to: 'users#avatar_destroy', via: :delete diff --git a/test/controllers/organization_controller_test.rb b/test/controllers/organization_controller_test.rb index 1ba5f2849..67e844c06 100644 --- a/test/controllers/organization_controller_test.rb +++ b/test/controllers/organization_controller_test.rb @@ -14,17 +14,6 @@ class OrganizationControllerTest < ActionDispatch::IntegrationTest UserInfo.current_user_id = 1 - @backup_admin = User.create_or_update( - login: 'backup-admin', - firstname: 'Backup', - lastname: 'Agent', - email: 'backup-admin@example.com', - password: 'adminpw', - active: true, - roles: roles, - groups: groups, - ) - @admin = User.create_or_update( login: 'rest-admin', firstname: 'Rest', @@ -510,4 +499,110 @@ class OrganizationControllerTest < ActionDispatch::IntegrationTest end + test '05.01 csv example - customer no access' do + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer1@example.com', 'customer1pw') + + get '/api/v1/organizations/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(401) + result = JSON.parse(@response.body) + assert_equal('Not authorized (user)!', result['error']) + end + + test '05.02 csv example - admin access' do + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + get '/api/v1/organizations/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(200) + + rows = CSV.parse(@response.body) + header = rows.shift + + assert_equal('id', header[0]) + assert_equal('name', header[1]) + assert_equal('shared', header[2]) + assert_equal('domain', header[3]) + assert_equal('domain_assignment', header[4]) + assert_equal('active', header[5]) + assert_equal('note', header[6]) + assert(header.include?('members')) + end + + test '05.03 csv import - admin access' do + + UserInfo.current_user_id = 1 + customer1 = User.create_or_update( + login: 'customer1-members@example.com', + firstname: 'Member', + lastname: 'Customer', + email: 'customer1-members@example.com', + password: 'customerpw', + active: true, + ) + customer2 = User.create_or_update( + login: 'customer2-members@example.com', + firstname: 'Member', + lastname: 'Customer', + email: 'customer2-members@example.com', + password: 'customerpw', + active: true, + ) + UserInfo.current_user_id = nil + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + # invalid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'organization_simple_col_not_existing.csv'), 'text/csv') + post '/api/v1/organizations/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('failed', result['result']) + assert_equal(2, result['errors'].count) + assert_equal("Line 1: unknown attribute 'name2' for Organization.", result['errors'][0]) + assert_equal("Line 2: unknown attribute 'name2' for Organization.", result['errors'][1]) + + # valid file try + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'organization_simple.csv'), 'text/csv') + post '/api/v1/organizations/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + assert_nil(Organization.find_by(name: 'organization-member-import1')) + assert_nil(Organization.find_by(name: 'organization-member-import2')) + + # valid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'organization_simple.csv'), 'text/csv') + post '/api/v1/organizations/import', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_nil(result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + organization1 = Organization.find_by(name: 'organization-member-import1') + assert(organization1) + assert_equal(organization1.name, 'organization-member-import1') + assert_equal(organization1.members.count, 1) + assert_equal(organization1.members.first.login, customer1.login) + assert_equal(organization1.active, true) + organization2 = Organization.find_by(name: 'organization-member-import2') + assert(organization2) + assert_equal(organization2.name, 'organization-member-import2') + assert_equal(organization2.members.count, 1) + assert_equal(organization2.members.first.login, customer2.login) + assert_equal(organization2.active, false) + + end + end diff --git a/test/controllers/text_module_controller_test.rb b/test/controllers/text_module_controller_test.rb new file mode 100644 index 000000000..077a18261 --- /dev/null +++ b/test/controllers/text_module_controller_test.rb @@ -0,0 +1,157 @@ + +require 'test_helper' +require 'rake' + +class TextModuleControllerTest < 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: 'rest-admin', + firstname: 'Rest', + lastname: 'Agent', + email: 'rest-admin@example.com', + password: 'adminpw', + active: true, + roles: roles, + groups: groups, + ) + + # create agent + roles = Role.where(name: 'Agent') + @agent = User.create_or_update( + login: 'rest-agent@example.com', + firstname: 'Rest', + lastname: 'Agent', + email: 'rest-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: 'rest-customer1@example.com', + firstname: 'Rest', + lastname: 'Customer1', + email: 'rest-customer1@example.com', + password: 'customer1pw', + active: true, + roles: roles, + ) + + # create customer + @customer_with_org = User.create_or_update( + login: 'rest-customer2@example.com', + firstname: 'Rest', + lastname: 'Customer2', + email: 'rest-customer2@example.com', + password: 'customer2pw', + active: true, + roles: roles, + ) + + UserInfo.current_user_id = nil + end + + test '05.01 csv example - customer no access' do + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer1@example.com', 'customer1pw') + + get '/api/v1/text_modules/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(401) + result = JSON.parse(@response.body) + assert_equal('Not authorized (user)!', result['error']) + end + + test '05.02 csv example - admin access' do + TextModule.load('en-en') + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + get '/api/v1/text_modules/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(200) + rows = CSV.parse(@response.body) + header = rows.shift + + assert_equal('id', header[0]) + assert_equal('name', header[1]) + assert_equal('keywords', header[2]) + assert_equal('content', header[3]) + assert_equal('note', header[4]) + assert_equal('active', header[5]) + assert_not(header.include?('organization')) + assert_not(header.include?('priority')) + assert_not(header.include?('state')) + assert_not(header.include?('owner')) + assert_not(header.include?('customer')) + end + + test '05.03 csv import - admin access' do + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + # invalid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'text_module_simple_col_not_existing.csv'), 'text/csv') + post '/api/v1/text_modules/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('failed', result['result']) + assert_equal(2, result['errors'].count) + assert_equal("Line 1: unknown attribute 'keywords2' for TextModule.", result['errors'][0]) + assert_equal("Line 2: unknown attribute 'keywords2' for TextModule.", result['errors'][1]) + + # valid file try + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'text_module_simple.csv'), 'text/csv') + post '/api/v1/text_modules/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + assert_nil(TextModule.find_by(name: 'some name1')) + assert_nil(TextModule.find_by(name: 'some name2')) + + # valid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'text_module_simple.csv'), 'text/csv') + post '/api/v1/text_modules/import', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_nil(result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + text_module1 = TextModule.find_by(name: 'some name1') + assert(text_module1) + assert_equal(text_module1.name, 'some name1') + assert_equal(text_module1.keywords, 'keyword1') + assert_equal(text_module1.content, 'some
content1') + assert_equal(text_module1.active, true) + text_module2 = TextModule.find_by(name: 'some name2') + assert(text_module2) + assert_equal(text_module2.name, 'some name2') + assert_equal(text_module2.keywords, 'keyword2') + assert_equal(text_module2.content, 'some content
test123') + assert_equal(text_module2.active, true) + + end + +end diff --git a/test/controllers/user_controller_test.rb b/test/controllers/user_controller_test.rb index 1ff94a176..d6a365bb7 100644 --- a/test/controllers/user_controller_test.rb +++ b/test/controllers/user_controller_test.rb @@ -957,4 +957,93 @@ class UserControllerTest < ActionDispatch::IntegrationTest end + test '05.01 csv example - customer no access' do + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-customer1@example.com', 'customer1pw') + + get '/api/v1/users/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(401) + result = JSON.parse(@response.body) + assert_equal('Not authorized (user)!', result['error']) + end + + test '05.02 csv example - admin access' do + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + get '/api/v1/users/import_example', params: {}, headers: @headers.merge('Authorization' => credentials) + assert_response(200) + + rows = CSV.parse(@response.body) + header = rows.shift + + assert_equal('id', header[0]) + assert_equal('login', header[1]) + assert_equal('firstname', header[2]) + assert_equal('lastname', header[3]) + assert_equal('email', header[4]) + assert(header.include?('organization')) + end + + test '05.03 csv import - admin access' do + + credentials = ActionController::HttpAuthentication::Basic.encode_credentials('rest-admin@example.com', 'adminpw') + + # invalid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'user_simple_col_not_existing.csv'), 'text/csv') + post '/api/v1/users/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('failed', result['result']) + assert_equal(2, result['errors'].count) + assert_equal("Line 1: unknown attribute 'firstname2' for User.", result['errors'][0]) + assert_equal("Line 2: unknown attribute 'firstname2' for User.", result['errors'][1]) + + # valid file try + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'user_simple.csv'), 'text/csv') + post '/api/v1/users/import?try=true', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_equal('true', result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + assert_nil(User.find_by(login: 'user-simple-import1')) + assert_nil(User.find_by(login: 'user-simple-import2')) + + # valid file + csv_file = ::Rack::Test::UploadedFile.new(Rails.root.join('test', 'fixtures', 'csv', 'user_simple.csv'), 'text/csv') + post '/api/v1/users/import', params: { file: csv_file }, headers: { 'Authorization' => credentials } + assert_response(200) + result = JSON.parse(@response.body) + assert_equal(Hash, result.class) + + assert_nil(result['try']) + assert_equal(2, result['records'].count) + assert_equal('success', result['result']) + + user1 = User.find_by(login: 'user-simple-import1') + assert(user1) + assert_equal(user1.login, 'user-simple-import1') + assert_equal(user1.firstname, 'firstname-simple-import1') + assert_equal(user1.lastname, 'lastname-simple-import1') + assert_equal(user1.email, 'user-simple-import1@example.com') + assert_equal(user1.active, true) + user2 = User.find_by(login: 'user-simple-import2') + assert(user2) + assert_equal(user2.login, 'user-simple-import2') + assert_equal(user2.firstname, 'firstname-simple-import2') + assert_equal(user2.lastname, 'lastname-simple-import2') + assert_equal(user2.email, 'user-simple-import2@example.com') + assert_equal(user2.active, false) + + user1.destroy! + user2.destroy! + end + end diff --git a/test/fixtures/csv/organization_simple.csv b/test/fixtures/csv/organization_simple.csv new file mode 100644 index 000000000..3874c91d7 --- /dev/null +++ b/test/fixtures/csv/organization_simple.csv @@ -0,0 +1,4 @@ +id;name;members;active +;organization-member-import1;customer1-members@example.com +;organization-member-import2;customer2-members@example.com;false + diff --git a/test/fixtures/csv/organization_simple_col_not_existing.csv b/test/fixtures/csv/organization_simple_col_not_existing.csv new file mode 100644 index 000000000..2d3953e81 --- /dev/null +++ b/test/fixtures/csv/organization_simple_col_not_existing.csv @@ -0,0 +1,3 @@ +id;name2;shared;domain;domain_assignment;active;note +;org-simple-import1;true;org-simple-import1.example.com;false;true;some note1 +;org-simple-import2;true;org-simple-import2.example.com;false;false;some note2 diff --git a/test/fixtures/csv/text_module_simple.csv b/test/fixtures/csv/text_module_simple.csv new file mode 100644 index 000000000..cb50d27b2 --- /dev/null +++ b/test/fixtures/csv/text_module_simple.csv @@ -0,0 +1,4 @@ +name;keywords;content;note;active; +some name1;keyword1;"some +content1";-; +some name2;keyword2;some content
test123 \ No newline at end of file diff --git a/test/fixtures/csv/text_module_simple_col_not_existing.csv b/test/fixtures/csv/text_module_simple_col_not_existing.csv new file mode 100644 index 000000000..1ad78437d --- /dev/null +++ b/test/fixtures/csv/text_module_simple_col_not_existing.csv @@ -0,0 +1,3 @@ +name;keywords2;content;note;active; +some name1;keyword1;"some content1";-; +some name2;keyword2;some content
test123 \ No newline at end of file diff --git a/test/fixtures/csv/user_simple.csv b/test/fixtures/csv/user_simple.csv new file mode 100644 index 000000000..b90294703 --- /dev/null +++ b/test/fixtures/csv/user_simple.csv @@ -0,0 +1,3 @@ +login;firstname;lastname;email;active; +user-simple-import1;firstname-simple-import1;lastname-simple-import1;user-simple-import1@example.com;true +user-simple-import2;firstname-simple-import2;lastname-simple-import2;user-simple-import2@example.com;false diff --git a/test/fixtures/csv/user_simple_col_not_existing.csv b/test/fixtures/csv/user_simple_col_not_existing.csv new file mode 100644 index 000000000..d112b5932 --- /dev/null +++ b/test/fixtures/csv/user_simple_col_not_existing.csv @@ -0,0 +1,3 @@ +login;firstname2;lastname;email;active; +user-simple-import1;firstname-simple-import1;lastname-simple-import1;user-simple-import1@example.com;true +user-simple-import2;firstname-simple-import2;lastname-simple-import2;user-simple-import2@example.com;false diff --git a/test/unit/organization_csv_import_test.rb b/test/unit/organization_csv_import_test.rb new file mode 100644 index 000000000..9979dd435 --- /dev/null +++ b/test/unit/organization_csv_import_test.rb @@ -0,0 +1,209 @@ + +require 'test_helper' + +class OrganizationCsvImportTest < ActiveSupport::TestCase + + test 'import example verify' do + csv_string = Organization.csv_example + + rows = CSV.parse(csv_string) + header = rows.shift + assert_equal('id', header[0]) + assert_equal('name', header[1]) + assert_equal('shared', header[2]) + assert_equal('domain', header[3]) + assert_equal('domain_assignment', header[4]) + assert_equal('active', header[5]) + assert_equal('note', header[6]) + assert(header.include?('members')) + end + + test 'simple import' do + + csv_string = "id;name;shared;domain;domain_assignment;active;note\n;org-simple-import1;true;org-simple-import1.example.com;false;true;some note1\n;org-simple-import2;true;org-simple-import2.example.com;false;false;some note2\n" + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(Organization.find_by(name: 'org-simple-import1')) + assert_nil(Organization.find_by(name: 'org-simple-import2')) + + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + organization1 = Organization.find_by(name: 'org-simple-import1') + assert(organization1) + assert_equal(organization1.name, 'org-simple-import1') + assert_equal(organization1.shared, true) + assert_equal(organization1.domain, 'org-simple-import1.example.com') + assert_equal(organization1.domain_assignment, false) + assert_equal(organization1.note, 'some note1') + assert_equal(organization1.active, true) + organization2 = Organization.find_by(name: 'org-simple-import2') + assert(organization2) + assert_equal(organization2.name, 'org-simple-import2') + assert_equal(organization2.shared, true) + assert_equal(organization2.domain, 'org-simple-import2.example.com') + assert_equal(organization2.domain_assignment, false) + assert_equal(organization2.note, 'some note2') + assert_equal(organization2.active, false) + + organization1.destroy! + organization2.destroy! + end + + test 'simple import with invalid id' do + + csv_string = "id;name;shared;domain;domain_assignment;active;note;\n999999999;organization-simple-invalid_id-import1;\n;organization-simple-invalid_id-import2;\n" + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(1, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown record with id '999999999' for Organization.", result[:errors][0]) + + assert_nil(Organization.find_by(name: 'organization-simple-invalid_id-import1')) + assert_nil(Organization.find_by(name: 'organization-simple-invalid_id-import2')) + + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(1, result[:records].count) + assert_equal('failed', result[:result]) + + assert_nil(Organization.find_by(name: 'organization-simple-invalid_id-import1')) + + organization2 = Organization.find_by(name: 'organization-simple-invalid_id-import2') + assert(organization2) + assert_equal(organization2.name, 'organization-simple-invalid_id-import2') + assert_equal(organization2.active, true) + + organization2.destroy! + end + + test 'simple import with members' do + UserInfo.current_user_id = 1 + + name = rand(999_999_999) + customer1 = User.create_or_update( + login: "customer1-members#{name}@example.com", + firstname: 'Member', + lastname: "Customer#{name}", + email: "customer1-members#{name}@example.com", + password: 'customerpw', + active: true, + ) + customer2 = User.create_or_update( + login: "customer2-members#{name}@example.com", + firstname: 'Member', + lastname: "Customer#{name}", + email: "customer2-members#{name}@example.com", + password: 'customerpw', + active: true, + ) + + csv_string = "id;name;members;\n;organization-member-import1;\n;organization-member-import2;#{customer1.email}\n;;#{customer2.email}" + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(Organization.find_by(name: 'organization-member-import1')) + assert_nil(Organization.find_by(name: 'organization-member-import2')) + + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + organization1 = Organization.find_by(name: 'organization-member-import1') + assert(organization1) + assert_equal(organization1.name, 'organization-member-import1') + assert_equal(organization1.members.count, 0) + organization2 = Organization.find_by(name: 'organization-member-import2') + assert(organization2) + assert_equal(organization2.name, 'organization-member-import2') + assert_equal(organization2.members.count, 2) + + customer1.destroy! + customer2.destroy! + organization1.destroy! + organization2.destroy! + end + + test 'invalid attributes' do + + csv_string = "name;note;not existing\norganization-invalid-import1;some note;abc\norganization-invalid-import2;some other note;123; with not exsiting header\n" + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'not existing' for Organization.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'not existing' for Organization.", result[:errors][1]) + + assert_nil(Organization.find_by(name: 'organization-invalid-import1')) + assert_nil(Organization.find_by(name: 'organization-invalid-import2')) + + result = Organization.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'not existing' for Organization.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'not existing' for Organization.", result[:errors][1]) + + assert_nil(Organization.find_by(name: 'organization-invalid-import1')) + assert_nil(Organization.find_by(name: 'organization-invalid-import2')) + end + +end diff --git a/test/unit/text_module_csv_import_test.rb b/test/unit/text_module_csv_import_test.rb new file mode 100644 index 000000000..af0208284 --- /dev/null +++ b/test/unit/text_module_csv_import_test.rb @@ -0,0 +1,72 @@ + +require 'test_helper' + +class TextModuleCsvImportTest < ActiveSupport::TestCase + + test 'import example verify' do + TextModule.load('en-en') + csv_string = TextModule.csv_example + + rows = CSV.parse(csv_string) + header = rows.shift + assert_equal('id', header[0]) + assert_equal('name', header[1]) + assert_equal('keywords', header[2]) + assert_equal('content', header[3]) + assert_equal('note', header[4]) + assert_equal('active', header[5]) + assert_not(header.include?('organization')) + assert_not(header.include?('priority')) + assert_not(header.include?('state')) + assert_not(header.include?('owner')) + assert_not(header.include?('customer')) + end + + test 'simple import' do + + csv_string = "name;keywords;content;note;active;\nsome name1;keyword1;\"some\ncontent1\";-;\nsome name2;keyword2;some content
test123\n" + result = TextModule.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(TextModule.find_by(name: 'some name1')) + assert_nil(TextModule.find_by(name: 'some name2')) + + result = TextModule.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + text_module1 = TextModule.find_by(name: 'some name1') + assert(text_module1) + assert_equal(text_module1.name, 'some name1') + assert_equal(text_module1.keywords, 'keyword1') + assert_equal(text_module1.content, 'some
content1') + assert_equal(text_module1.active, true) + text_module2 = TextModule.find_by(name: 'some name2') + assert(text_module2) + assert_equal(text_module2.name, 'some name2') + assert_equal(text_module2.keywords, 'keyword2') + assert_equal(text_module2.content, 'some content
test123') + assert_equal(text_module2.active, true) + + text_module1.destroy! + text_module2.destroy! + end + +end diff --git a/test/unit/ticket_csv_import_test.rb b/test/unit/ticket_csv_import_test.rb new file mode 100644 index 000000000..fdd7ce8d8 --- /dev/null +++ b/test/unit/ticket_csv_import_test.rb @@ -0,0 +1,172 @@ + +require 'test_helper' + +class TicketCsvImportTest < ActiveSupport::TestCase + + test 'import example verify' do + csv_string = Ticket.csv_example + + rows = CSV.parse(csv_string) + header = rows.shift + assert_equal('id', header[0]) + assert_equal('number', header[1]) + assert_equal('title', header[2]) + assert_equal('note', header[3]) + assert_equal('first_response_at', header[4]) + assert_equal('first_response_escalation_at', header[5]) + assert(header.include?('organization')) + assert(header.include?('priority')) + assert(header.include?('state')) + assert(header.include?('owner')) + assert(header.include?('customer')) + + end + + test 'simple import' do + + csv_string = "id;number;title;state;priority;owner;customer;group;note\n;123456;some title1;new;2 normal;-;nicole.braun@zammad.org;Users;some note1\n;123457;some title2;closed;1 low;admin@example.com;nicole.braun@zammad.org;Users;some note2\n" + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(Ticket.find_by(number: '123456')) + assert_nil(Ticket.find_by(number: '123457')) + + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + ticket1 = Ticket.find_by(number: '123456') + assert(ticket1) + assert_equal(ticket1.number, '123456') + assert_equal(ticket1.title, 'some title1') + assert_equal(ticket1.state.name, 'new') + assert_equal(ticket1.priority.name, '2 normal') + assert_equal(ticket1.owner.login, '-') + assert_equal(ticket1.customer.login, 'nicole.braun@zammad.org') + assert_equal(ticket1.note, 'some note1') + ticket2 = Ticket.find_by(number: '123457') + assert(ticket2) + assert_equal(ticket2.number, '123457') + assert_equal(ticket2.title, 'some title2') + assert_equal(ticket2.state.name, 'closed') + assert_equal(ticket2.priority.name, '1 low') + assert_equal(ticket2.owner.login, 'admin@example.com') + assert_equal(ticket2.customer.login, 'nicole.braun@zammad.org') + assert_equal(ticket2.note, 'some note2') + + ticket1.destroy! + ticket2.destroy! + end + + test 'simple import with invalid id' do + + csv_string = "id;number;title;state;priority;owner;customer;group;note\n999999999;123456;some title1;new;2 normal;-;nicole.braun@zammad.org;Users;some note1\n;123457;some title2;closed;1 low;admin@example.com;nicole.braun@zammad.org;Users;some note2\n" + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(1, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown record with id '999999999' for Ticket.", result[:errors][0]) + + assert_nil(Ticket.find_by(number: '123456')) + assert_nil(Ticket.find_by(number: '123457')) + + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(1, result[:records].count) + assert_equal('failed', result[:result]) + + assert_nil(Ticket.find_by(number: '123456')) + + ticket2 = Ticket.find_by(number: '123457') + assert(ticket2) + assert_equal(ticket2.title, 'some title2') + assert_equal(ticket2.note, 'some note2') + + csv_string = "id;number;title;state;priority;owner;customer;group;note\n999999999;123456;some title1;new;2 normal;-;nicole.braun@zammad.org;Users;some note1\n;123457;some title22;closed;1 low;admin@example.com;nicole.braun@zammad.org;Users;some note22\n" + + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(1, result[:records].count) + assert_equal('failed', result[:result]) + + assert_nil(Ticket.find_by(number: '123456')) + + ticket2 = Ticket.find_by(number: '123457') + assert(ticket2) + assert_equal(ticket2.title, 'some title22') + assert_equal(ticket2.note, 'some note22') + + ticket2.destroy! + end + + test 'invalid attributes' do + + csv_string = "id;number;not_existing;state;priority;owner;customer;group;note\n;123456;some title1;new;2 normal;-;nicole.braun@zammad.org;Users;some note1\n;123457;some title2;closed;1 low;admin@example.com;nicole.braun@zammad.org;Users;some note2\n" + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'not_existing' for Ticket.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'not_existing' for Ticket.", result[:errors][1]) + + assert_nil(Ticket.find_by(number: '123456')) + assert_nil(Ticket.find_by(number: '123457')) + + result = Ticket.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'not_existing' for Ticket.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'not_existing' for Ticket.", result[:errors][1]) + + assert_nil(Ticket.find_by(number: '123456')) + assert_nil(Ticket.find_by(number: '123457')) + end + +end diff --git a/test/unit/user_csv_import_test.rb b/test/unit/user_csv_import_test.rb new file mode 100644 index 000000000..c6d111b8d --- /dev/null +++ b/test/unit/user_csv_import_test.rb @@ -0,0 +1,416 @@ + +require 'test_helper' + +class UserCsvImportTest < ActiveSupport::TestCase + + test 'import example verify' do + csv_string = User.csv_example + + rows = CSV.parse(csv_string) + header = rows.shift + + assert_equal('id', header[0]) + assert_equal('login', header[1]) + assert_equal('firstname', header[2]) + assert_equal('lastname', header[3]) + assert_equal('email', header[4]) + assert(header.include?('organization')) + end + + test 'simple import' do + + csv_string = "login;firstname;lastname;email;active;\nuser-simple-import1;firstname-simple-import1;lastname-simple-import1;user-simple-import1@example.com;true\nuser-simple-import2;firstname-simple-import2;lastname-simple-import2;user-simple-import2@example.com;false\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(User.find_by(login: 'user-simple-import1')) + assert_nil(User.find_by(login: 'user-simple-import2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + user1 = User.find_by(login: 'user-simple-import1') + assert(user1) + assert_equal(user1.login, 'user-simple-import1') + assert_equal(user1.firstname, 'firstname-simple-import1') + assert_equal(user1.lastname, 'lastname-simple-import1') + assert_equal(user1.email, 'user-simple-import1@example.com') + assert_equal(user1.active, true) + user2 = User.find_by(login: 'user-simple-import2') + assert(user2) + assert_equal(user2.login, 'user-simple-import2') + assert_equal(user2.firstname, 'firstname-simple-import2') + assert_equal(user2.lastname, 'lastname-simple-import2') + assert_equal(user2.email, 'user-simple-import2@example.com') + assert_equal(user2.active, false) + + user1.destroy! + user2.destroy! + end + + test 'simple import with invalid id' do + + csv_string = "id;login;firstname;lastname;email;active;\n999999999;user-simple-invalid_id-import1;firstname-simple-import1;lastname-simple-import1;user-simple-invalid_id-import1@example.com;true\n;user-simple-invalid_id-import2;firstname-simple-import2;lastname-simple-import2;user-simple-invalid_id-import2@example.com;false\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(1, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown record with id '999999999' for User.", result[:errors][0]) + + assert_nil(User.find_by(login: 'user-simple-invalid_id-import1')) + assert_nil(User.find_by(login: 'user-simple-invalid_id-import2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(1, result[:records].count) + assert_equal('failed', result[:result]) + + assert_nil(User.find_by(login: 'user-simple-invalid_id-import1')) + + user2 = User.find_by(login: 'user-simple-invalid_id-import2') + assert(user2) + assert_equal(user2.login, 'user-simple-invalid_id-import2') + assert_equal(user2.firstname, 'firstname-simple-import2') + assert_equal(user2.lastname, 'lastname-simple-import2') + assert_equal(user2.email, 'user-simple-invalid_id-import2@example.com') + assert_equal(user2.active, false) + + user2.destroy! + end + + test 'simple import with read only id' do + + csv_string = "id;login;firstname;lastname;email;active;\n1;user-simple-readonly_id-import1;firstname-simple-import1;lastname-simple-import1;user-simple-readonly_id-import1@example.com;true\n;user-simple-readonly_id-import2;firstname-simple-import2;lastname-simple-import2;user-simple-readonly_id-import2@example.com;false\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(1, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unable to update record with id '1' for User.", result[:errors][0]) + + assert_nil(User.find_by(login: 'user-simple-readonly_id-import1')) + assert_nil(User.find_by(login: 'user-simple-readonly_id-import2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(1, result[:records].count) + assert_equal('failed', result[:result]) + + assert_nil(User.find_by(login: 'user-simple-readonly_id-import1')) + + user2 = User.find_by(login: 'user-simple-readonly_id-import2') + assert(user2) + assert_equal(user2.login, 'user-simple-readonly_id-import2') + assert_equal(user2.firstname, 'firstname-simple-import2') + assert_equal(user2.lastname, 'lastname-simple-import2') + assert_equal(user2.email, 'user-simple-readonly_id-import2@example.com') + assert_equal(user2.active, false) + + user2.destroy! + end + + test 'simple import with roles' do + UserInfo.current_user_id = 1 + + admin = User.create_or_update( + login: 'admin1@example.com', + firstname: 'Admin', + lastname: '1', + email: 'admin1@example.com', + password: 'agentpw', + active: true, + roles: Role.where(name: 'Admin'), + ) + + csv_string = "login;firstname;lastname;email;roles;\nuser-role-import1;firstname-role-import1;lastname-role-import1;user-role-import1@example.com;Customer;\nuser-role-import2;firstname-role-import2;lastname-role-import2;user-role-import2@example.com;Agent\n;;;;Admin" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(User.find_by(login: 'user-role-import1')) + assert_nil(User.find_by(login: 'user-role-import2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + user1 = User.find_by(login: 'user-role-import1') + assert(user1) + assert_equal(user1.login, 'user-role-import1') + assert_equal(user1.firstname, 'firstname-role-import1') + assert_equal(user1.lastname, 'lastname-role-import1') + assert_equal(user1.email, 'user-role-import1@example.com') + assert_equal(user1.roles.count, 1) + user2 = User.find_by(login: 'user-role-import2') + assert(user2) + assert_equal(user2.login, 'user-role-import2') + assert_equal(user2.firstname, 'firstname-role-import2') + assert_equal(user2.lastname, 'lastname-role-import2') + assert_equal(user2.email, 'user-role-import2@example.com') + assert_equal(user2.roles.count, 2) + + user1.destroy! + user2.destroy! + admin.destroy! + end + + test 'simple import + fixed params' do + + csv_string = "login;firstname;lastname;email\nuser-simple-import-fixed1;firstname-simple-import-fixed1;lastname-simple-import-fixed1;user-simple-import-fixed1@example.com\nuser-simple-import-fixed2;firstname-simple-import-fixed2;lastname-simple-import-fixed2;user-simple-import-fixed2@example.com\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + fixed_params: { + note: 'some note', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(User.find_by(login: 'user-simple-import-fixed1')) + assert_nil(User.find_by(login: 'user-simple-import-fixed2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + fixed_params: { + note: 'some note', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:records].count) + assert_equal('success', result[:result]) + + user1 = User.find_by(login: 'user-simple-import-fixed1') + user2 = User.find_by(login: 'user-simple-import-fixed2') + assert(user1) + assert_equal('some note', user1.note) + assert_equal('user-simple-import-fixed1', user1.login) + assert_equal('firstname-simple-import-fixed1', user1.firstname) + assert_equal('lastname-simple-import-fixed1', user1.lastname) + assert_equal('user-simple-import-fixed1@example.com', user1.email) + + assert(user2) + assert_equal('some note', user2.note) + assert_equal('user-simple-import-fixed2', user2.login) + assert_equal('firstname-simple-import-fixed2', user2.firstname) + assert_equal('lastname-simple-import-fixed2', user2.lastname) + assert_equal('user-simple-import-fixed2@example.com', user2.email) + + user1.destroy! + user2.destroy! + end + + test 'duplicate import' do + + csv_string = "login;firstname;lastname;email\nuser-duplicate-import1;firstname-duplicate-import1;firstname-duplicate-import1;user-duplicate-import1@example.com\nuser-duplicate-import2;firstname-duplicate-import2;firstname-duplicate-import2;user-duplicate-import2@example.com\nuser-duplicate-import2;firstname-duplicate-import3;firstname-duplicate-import3;user-duplicate-import3@example.com" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(3, result[:records].count) + assert_equal('success', result[:result]) + + assert_nil(User.find_by(login: 'user-duplicate-import1')) + assert_nil(User.find_by(login: 'user-duplicate-import2')) + assert_nil(User.find_by(login: 'user-duplicate-import3')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(3, result[:records].count) + assert_equal('success', result[:result]) + + assert(User.find_by(login: 'user-duplicate-import1')) + assert(User.find_by(login: 'user-duplicate-import2')) + assert_nil(User.find_by(login: 'user-duplicate-import3')) + + User.find_by(login: 'user-duplicate-import1').destroy! + User.find_by(login: 'user-duplicate-import2').destroy! + end + + test 'invalid attributes' do + + csv_string = "login;firstname2;lastname;email\nuser-invalid-import1;firstname-invalid-import1;firstname-invalid-import1;user-invalid-import1@example.com\nuser-invalid-import2;firstname-invalid-import2;firstname-invalid-import2;user-invalid-import2@example.com\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'firstname2' for User.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'firstname2' for User.", result[:errors][1]) + + assert_nil(User.find_by(login: 'user-invalid-import1')) + assert_nil(User.find_by(login: 'user-invalid-import2')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_equal("Line 1: unknown attribute 'firstname2' for User.", result[:errors][0]) + assert_equal("Line 2: unknown attribute 'firstname2' for User.", result[:errors][1]) + + assert_nil(User.find_by(login: 'user-invalid-import1')) + assert_nil(User.find_by(login: 'user-invalid-import2')) + end + + test 'reference import' do + + csv_string = "login;firstname;lastname;email;organization\nuser-reference-import1;firstname-reference-import1;firstname-reference-import1;user-reference-import1@example.com;organization-reference-import1\nuser-reference-import2;firstname-reference-import2;firstname-reference-import2;user-reference-import2@example.com;organization-reference-import2\nuser-reference-import3;firstname-reference-import3;firstname-reference-import3;user-reference-import3@example.com;Zammad Foundation\n" + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + assert_nil(User.find_by(login: 'user-reference-import1')) + assert_nil(User.find_by(login: 'user-reference-import2')) + assert_nil(User.find_by(login: 'user-reference-import3')) + assert_equal("Line 1: No lookup value found for 'organization': \"organization-reference-import1\"", result[:errors][0]) + assert_equal("Line 2: No lookup value found for 'organization': \"organization-reference-import2\"", result[:errors][1]) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(2, result[:errors].count) + assert_equal('failed', result[:result]) + + assert_nil(User.find_by(login: 'user-reference-import1')) + assert_nil(User.find_by(login: 'user-reference-import2')) + assert(User.find_by(login: 'user-reference-import3')) + assert_equal("Line 1: No lookup value found for 'organization': \"organization-reference-import1\"", result[:errors][0]) + assert_equal("Line 2: No lookup value found for 'organization': \"organization-reference-import2\"", result[:errors][1]) + + UserInfo.current_user_id = 1 + orgaization1 = Organization.create_if_not_exists(name: 'organization-reference-import1') + orgaization2 = Organization.create_if_not_exists(name: 'organization-reference-import2') + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: true, + ) + assert_equal(true, result[:try]) + assert_equal(0, result[:errors].count) + assert_equal('success', result[:result]) + assert_nil(User.find_by(login: 'user-reference-import1')) + assert_nil(User.find_by(login: 'user-reference-import2')) + assert(User.find_by(login: 'user-reference-import3')) + + result = User.csv_import( + string: csv_string, + parse_params: { + col_sep: ';', + }, + try: false, + ) + assert_equal(false, result[:try]) + assert_equal(0, result[:errors].count) + assert_equal('success', result[:result]) + + assert(User.find_by(login: 'user-reference-import1')) + assert(User.find_by(login: 'user-reference-import2')) + assert(User.find_by(login: 'user-reference-import3')) + + User.find_by(login: 'user-reference-import1').destroy! + User.find_by(login: 'user-reference-import2').destroy! + User.find_by(login: 'user-reference-import3').destroy! + + orgaization1.destroy! + orgaization2.destroy! + end + +end diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb index a6842b556..18414c71f 100644 --- a/test/unit/user_test.rb +++ b/test/unit/user_test.rb @@ -570,7 +570,6 @@ class UserTest < ActiveSupport::TestCase test 'ensure roles' do name = rand(999_999_999) - admin = User.create_or_update( login: "admin-role#{name}@example.com", firstname: 'Role',