diff --git a/app/controllers/concerns/api_actionable.rb b/app/controllers/concerns/api_actionable.rb index 583bd3c64..9cd246240 100644 --- a/app/controllers/concerns/api_actionable.rb +++ b/app/controllers/concerns/api_actionable.rb @@ -73,32 +73,12 @@ def execute_api_actions end def handle_error(e) - clone_actions(:error_api_actions) execute_error_actions raise end def execute_error_actions - return if @api_resource.nil? - - error_api_actions = @api_resource.error_api_actions - - redirect_action = error_api_actions.where(action_type: 'redirect').last - error_api_actions.each do |action| - action.execute_action unless action.redirect? - end - - if redirect_action - redirect_action.update(lifecycle_stage: 'complete', lifecycle_message: redirect_action.redirect_url) - # Redirecting with JS is only needed when dealing with reCaptcha. - # reCaptcha related request is handled by ResourceController - if controller_name == "resource" - redirect_with_js(redirect_url) and return - else - redirect_to redirect_url and return - end - end - + ErrorApiAction.where(id: create_error_actions.map(&:id)).execute_model_context_api_actions @error_api_actions_exectuted = true end @@ -115,7 +95,19 @@ def clone_actions(action_name) return if @api_resource.nil? || @api_resource.new_record? @api_namespace.send(action_name).each do |action| - @api_resource.send(action_name).create(action.attributes.merge(custom_message: action.custom_message.to_s).except("id", "created_at", "updated_at", "api_namespace_id")) + @api_resource.send(action_name).create(action.attributes.merge(custom_message: action.custom_message.to_s, parent_id: action.id).except("id", "created_at", "updated_at", "api_namespace_id")) + end + end + + # api_resource doesn't get saved if there's any error + def create_error_actions + @api_namespace.error_api_actions.map do |action| + api_resource_json = { + properties: @api_resource.properties, + api_namespace_id: @api_namespace.id, + errors: @api_resource.errors.full_messages.to_sentence + } + ErrorApiAction.create(action.attributes.merge(custom_message: action.custom_message.to_s, parent_id: action.id, meta_data: { api_resource: api_resource_json }).except("id", "created_at", "updated_at", "api_namespace_id")) end end diff --git a/app/models/api_action.rb b/app/models/api_action.rb index 483f992ae..2d523e10a 100644 --- a/app/models/api_action.rb +++ b/app/models/api_action.rb @@ -6,7 +6,7 @@ class ApiAction < ApplicationRecord attr_encrypted :bearer_token attr_dynamic :email, :email_subject, :custom_message, :payload_mapping, :custom_headers, :request_url, :redirect_url - after_update :update_executed_actions_payload, if: Proc.new { api_namespace.present? && saved_change_to_payload_mapping? } + after_update :update_api_resource_actions, if: Proc.new { self.api_namespace_action? } belongs_to :api_namespace, optional: true belongs_to :api_resource, optional: true @@ -17,6 +17,11 @@ class ApiAction < ApplicationRecord enum redirect_type: { cms_page: 0, dynamic_url: 1 } + # api_namespace_action acts as class defination and api_resource_actions act as instance of api_namespace_action + has_many :api_resource_actions, class_name: 'ApiAction', foreign_key: :parent_id + + belongs_to :api_namespace_action, class_name: 'ApiAction', foreign_key: :parent_id, optional: true + EXECUTION_ORDER = { model_level: ['send_email', 'send_web_request', 'custom_action'], controller_level: ['serve_file', 'redirect'], @@ -38,44 +43,95 @@ def self.children ['new_api_actions', 'create_api_actions', 'show_api_actions', 'update_api_actions', 'destroy_api_actions', 'error_api_actions'] end - def execute_action + def execute_action(run_error_action = true) self.update(lifecycle_stage: 'executing') - send(action_type) + send(action_type, run_error_action) + end + + def self.execute_model_context_api_actions + api_actions = self.where(action_type: ApiAction::EXECUTION_ORDER[:model_level], lifecycle_stage: 'initialized') + + ApiAction::EXECUTION_ORDER[:model_level].each do |action_type| + if ApiAction.action_types[action_type] == ApiAction.action_types[:custom_action] + custom_actions = api_actions.where(action_type: 'custom_action') + custom_actions.each do |custom_action| + FireApiActionsJob.perform_async(custom_action.id, Current.user&.id, Current.visit&.id, Current.is_api_html_renderer_request) + end + elsif [ApiAction.action_types[:send_email], ApiAction.action_types[:send_web_request]].include?(ApiAction.action_types[action_type]) + api_actions.where(action_type: ApiAction.action_types[action_type]).each do |api_action| + FireApiActionsJob.perform_async(api_action.id, Current.user&.id, Current.visit&.id, Current.is_api_html_renderer_request) + end + end + end if api_actions.present? + end + + + def execute_error_actions(error) + self.update(lifecycle_stage: 'failed', lifecycle_message: error) + + return if type == 'ErrorApiAction' || api_resource.nil? + + api_resource.api_namespace.error_api_actions.each do |action| + api_resource.error_api_actions.create(action.attributes.merge(custom_message: action.custom_message.to_s).except("id", "created_at", "updated_at", "api_namespace_id")) + end + api_resource.error_api_actions.each(&:execute_action) + end + + def api_namespace_action? + api_namespace_id.present? + end + + def api_resource_action? + api_resource_id.present? end private - def update_executed_actions_payload - ApiAction.where(api_resource_id: api_namespace.api_resources.pluck(:id), payload_mapping: payload_mapping_previously_was, type: type, action_type: action_type).update_all(payload_mapping: payload_mapping) + def update_api_resource_actions + api_actions_to_update = self.api_resource_actions.where.not(lifecycle_stage: [:complete, :discarded]) + api_actions_to_update.update_all({ + payload_mapping: payload_mapping, + include_api_resource_data: include_api_resource_data, + redirect_url: redirect_url, + request_url: request_url, + position: position, + email: email, + file_snippet: file_snippet, + custom_headers: custom_headers, + http_method: http_method, + method_definition: method_definition, + email_subject: email_subject, + redirect_type: redirect_type, + }) + ActionText::RichText.where(record_type: 'ApiAction', record_id: api_actions_to_update.pluck(:id)).update_all(body: custom_message.to_s) end - def send_email + def send_email(run_error_action = true) begin ApiActionMailer.send_email(self).deliver_now self.update(lifecycle_stage: 'complete', lifecycle_message: email) - rescue => e - self.update(lifecycle_stage: 'failed', lifecycle_message: e.message) - execute_error_actions + rescue Exception => e + execute_error_actions(e.message) if run_error_action + raise end end - def send_web_request + def send_web_request(run_error_action = true) begin response = HTTParty.send(http_method.to_s, request_url_evaluated, { body: payload_mapping_evaluated, headers: request_headers }) if response.success? self.update(lifecycle_stage: 'complete', lifecycle_message: response.to_s) else - self.update(lifecycle_stage: 'failed', lifecycle_message: response.to_s) - execute_error_actions + execute_error_actions(response.to_s) end rescue => e - self.update(lifecycle_stage: 'failed', lifecycle_message: e.message) - execute_error_actions + execute_error_actions(e.message) if run_error_action + raise end end - def custom_action + def custom_action(run_error_action = true) begin custom_api_action = CustomApiAction.new eval("def custom_api_action.run_custom_action(api_action: , api_namespace: , api_resource: , current_visit: , current_user: nil); #{self.method_definition}; end") @@ -84,27 +140,17 @@ def custom_action self.update(lifecycle_stage: 'complete', lifecycle_message: response.to_json) rescue => e - self.update(lifecycle_stage: 'failed', lifecycle_message: e.message) - execute_error_actions + execute_error_actions(e.message) if run_error_action raise end end - def redirect;end + def redirect(run_error_action = true);end - def serve_file;end + def serve_file(run_error_action = true);end def request_headers - headers = custom_headers_evaluated.gsub('SECRET_BEARER_TOKEN', bearer_token) + headers = custom_headers_evaluated.gsub('SECRET_BEARER_TOKEN', bearer_token.to_s) { 'Content-Type' => 'application/json' }.merge(JSON.parse(headers)) end - - def execute_error_actions - return if type == 'ErrorApiAction' || api_resource.nil? - - api_resource.api_namespace.error_api_actions.each do |action| - api_resource.error_api_actions.create(action.attributes.merge(custom_message: action.custom_message.to_s).except("id", "created_at", "updated_at", "api_namespace_id")) - end - api_resource.error_api_actions.each(&:execute_action) - end end diff --git a/app/models/api_namespace.rb b/app/models/api_namespace.rb index 3b7f31b4b..1de7698ea 100755 --- a/app/models/api_namespace.rb +++ b/app/models/api_namespace.rb @@ -25,8 +25,6 @@ class ApiNamespace < ApplicationRecord has_many :api_actions, dependent: :destroy - has_many :executed_api_actions, through: :api_resources, class_name: 'ApiAction', source: :api_actions - has_many :new_api_actions, dependent: :destroy accepts_nested_attributes_for :new_api_actions, allow_destroy: true @@ -326,6 +324,10 @@ def self.import_as_json(json_str) end end + def executed_api_actions + ApiAction.where(api_resource_id: api_resources.pluck(:id)).or(ApiAction.where("meta_data->'api_resource' ->> 'api_namespace_id' = '#{self.id}'")) + end + def snippet(with_brackets: true) return unless self.api_form.present? diff --git a/app/models/api_resource.rb b/app/models/api_resource.rb index 83da068b2..80bc14343 100755 --- a/app/models/api_resource.rb +++ b/app/models/api_resource.rb @@ -44,9 +44,9 @@ class ApiResource < ApplicationRecord Arel.sql("api_resources.properties::text") end - def clone_api_actions(action_name) + def initialize_api_resource_actions(action_name) api_namespace.send(action_name).each do |action| - self.send(action_name).create(action.attributes.merge('custom_message' => action.custom_message.to_s, 'lifecycle_stage' => 'initialized').except("id", "created_at", "updated_at", "api_namespace_id")) + self.send(action_name).create(action.attributes.merge('custom_message' => action.custom_message.to_s, 'lifecycle_stage' => 'initialized', parent_id: action.id).except("id", "created_at", "updated_at", "api_namespace_id")) end end @@ -75,25 +75,7 @@ def tracked_user end def execute_model_context_api_actions(class_name) - api_actions = self.send(class_name).where(action_type: ApiAction::EXECUTION_ORDER[:model_level], lifecycle_stage: 'initialized') - - ApiAction::EXECUTION_ORDER[:model_level].each do |action_type| - if ApiAction.action_types[action_type] == ApiAction.action_types[:custom_action] - begin - custom_actions = api_actions.where(action_type: 'custom_action') - custom_actions.each do |custom_action| - custom_action.execute_action - end - rescue - # error-actions are executed already in api-action level. - nil - end - elsif [ApiAction.action_types[:send_email], ApiAction.action_types[:send_web_request]].include?(ApiAction.action_types[action_type]) - api_actions.where(action_type: ApiAction.action_types[action_type]).each do |api_action| - api_action.execute_action - end - end - end if api_actions.present? + self.send(class_name).execute_model_context_api_actions end private @@ -121,12 +103,12 @@ def set_creator end def execute_create_api_actions - clone_api_actions('create_api_actions') - FireApiActionsJob.perform_async(self.id, 'create_api_actions', Current.user&.id, Current.visit&.id, Current.is_api_html_renderer_request) + initialize_api_resource_actions('create_api_actions') + CreateApiAction.where(id: self.create_api_actions.pluck(:id)).execute_model_context_api_actions end def execute_update_api_actions - clone_api_actions('update_api_actions') - FireApiActionsJob.perform_async(self.id, 'update_api_actions', Current.user&.id, Current.visit&.id, Current.is_api_html_renderer_request) + initialize_api_resource_actions('update_api_actions') + UpdateApiAction.where(id: self.update_api_actions.pluck(:id)).execute_model_context_api_actions end end diff --git a/app/services/api_namespace/plugin/v1/subdomain_events_service.rb b/app/services/api_namespace/plugin/v1/subdomain_events_service.rb index fb613ddac..45d0c8914 100644 --- a/app/services/api_namespace/plugin/v1/subdomain_events_service.rb +++ b/app/services/api_namespace/plugin/v1/subdomain_events_service.rb @@ -34,6 +34,7 @@ def track_event body: "New Forum Post @ #{domain_with_fallback}.#{ENV['APP_HOST']} - created by: #{created_by_user.name} (#{created_by_user.email})" } end - ApiResourceSpawnJob.perform_async(api_namespace.id, resource_properties.to_json) + + api_namespace.api_resources.create!(properties: resource_properties) end end \ No newline at end of file diff --git a/app/sidekiq/api_resource_spawn_job.rb b/app/sidekiq/api_resource_spawn_job.rb deleted file mode 100644 index 9146b124c..000000000 --- a/app/sidekiq/api_resource_spawn_job.rb +++ /dev/null @@ -1,12 +0,0 @@ -class ApiResourceSpawnJob - include Sidekiq::Job - - def perform(api_namespace_id, json_string = "") - api_namespace = ApiNamespace.find(api_namespace_id) - api_resource = api_namespace.api_resources.create!( - # convert back to hash because sidekiq doesnt like taking a hash as an argument-- it prefers json instead - properties: JSON.parse(json_string).to_h - ) - api_resource.api_actions.each{|action| action.execute_action} - end -end diff --git a/app/sidekiq/fire_api_actions_job.rb b/app/sidekiq/fire_api_actions_job.rb index 2b02433fe..b083b506e 100644 --- a/app/sidekiq/fire_api_actions_job.rb +++ b/app/sidekiq/fire_api_actions_job.rb @@ -1,14 +1,26 @@ class FireApiActionsJob include Sidekiq::Job - def perform(api_resource_id, action_class, current_user_id, current_visit_id, is_api_html_renderer_request) + # run error actions only after final retries + sidekiq_retries_exhausted do |msg, exception| + set_current_visit(msg['args'][1], msg['args'][2], msg['args'][3]) do + ApiAction.find(msg['args'][0]).execute_error_actions(exception.message) + end + end + + def perform(action_id, current_user_id, current_visit_id, is_api_html_renderer_request) + FireApiActionsJob.set_current_visit(current_user_id, current_visit_id, is_api_html_renderer_request) do + api_action = ApiAction.find(action_id) + api_action.execute_action(false) if api_action.present? + end + end + + def self.set_current_visit(current_user_id, current_visit_id, is_api_html_renderer_request) current_user = User.find_by(id: current_user_id) current_visit = Ahoy::Visit.find_by(id: current_visit_id) Current.set(user: current_user, visit: current_visit, is_api_html_renderer_request: is_api_html_renderer_request) do - api_resource = ApiResource.find(api_resource_id) - - api_resource.execute_model_context_api_actions(action_class) + yield end end end diff --git a/app/views/comfy/admin/api_actions/index.html.haml b/app/views/comfy/admin/api_actions/index.html.haml index e750c0d93..7a77c19d9 100644 --- a/app/views/comfy/admin/api_actions/index.html.haml +++ b/app/views/comfy/admin/api_actions/index.html.haml @@ -29,7 +29,14 @@ - @api_actions.each do |api_action| %tr %td.px-3.py-2= link_to api_action.id, api_namespace_api_action_path(api_namespace_id: @api_namespace.id, id: api_action.id) - %td.px-3.py-2= link_to api_action.api_resource_id, api_namespace_resource_path(api_namespace_id: @api_namespace.id, id: api_action.api_resource_id) + %td.px-3.py-2 + - if api_action.api_resource_id + = link_to api_action.api_resource_id, api_namespace_resource_path(api_namespace_id: @api_namespace.id, id: api_action.api_resource_id) + - else + %div= api_action.meta_data.dig('api_resource', 'properties') + %div + %strong Error: + = api_action.meta_data.dig('api_resource', 'errors') %td.px-3= api_action.type %td.px-3= api_action.action_type %td.px-3 diff --git a/db/migrate/20220906063031_add_additional_data_to_api_actions.rb b/db/migrate/20220906063031_add_additional_data_to_api_actions.rb new file mode 100644 index 000000000..a00591e89 --- /dev/null +++ b/db/migrate/20220906063031_add_additional_data_to_api_actions.rb @@ -0,0 +1,5 @@ +class AddAdditionalDataToApiActions < ActiveRecord::Migration[6.1] + def change + add_column :api_actions, :meta_data, :jsonb, default: {} + end +end diff --git a/db/migrate/20230103105118_add_parent_id_to_api_actions.rb b/db/migrate/20230103105118_add_parent_id_to_api_actions.rb new file mode 100644 index 000000000..5e38e6d94 --- /dev/null +++ b/db/migrate/20230103105118_add_parent_id_to_api_actions.rb @@ -0,0 +1,5 @@ +class AddParentIdToApiActions < ActiveRecord::Migration[6.1] + def change + add_column :api_actions, :parent_id, :integer + end +end diff --git a/db/migrate/20230103123121_populate_api_action_parent_id.rb b/db/migrate/20230103123121_populate_api_action_parent_id.rb new file mode 100644 index 000000000..01a5ffe66 --- /dev/null +++ b/db/migrate/20230103123121_populate_api_action_parent_id.rb @@ -0,0 +1,34 @@ +class PopulateApiActionParentId < ActiveRecord::Migration[6.1] + # Since there is no effective way to determine the parent api_action for executed api actions, + # this method will only work for api_resource_actions whose parent api_namespace_actions are not updated after execution. + + def up + ApiNamespace.all.each do |api_namespace| + api_namespace.api_actions.each do |api_action| + api_namespace.executed_api_actions.where({ + payload_mapping: api_action.payload_mapping, + redirect_url: api_action.redirect_url, + request_url: api_action.request_url, + email: api_action.email, + custom_headers: api_action.custom_headers, + method_definition: api_action.method_definition, + email_subject: api_action.email_subject, + type: api_action.type, + action_type: api_action.action_type + }).update_all(parent_id: api_action.id) + end + end + + # For executed api actions whose parent_id couldn't be populated, can be updated manually. + # script to list orphan api actions + orphan_api_actions = [] + ApiNamespace.all.each do |api_namespace| + api_namespace.executed_api_actions.where(parent_id: nil).each { |api_action| orphan_api_actions << [api_namespace.id, api_action.id, api_action.api_resource_id]} + end + p orphan_api_actions.reject { |c| c.empty? } + end + + def down + ApiAction.all.update_all(parent_id: nil) + end +end diff --git a/db/schema.rb b/db/schema.rb index 9efa6d900..ce6ac7973 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2022_11_28_055836) do +ActiveRecord::Schema.define(version: 2023_01_03_123121) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -127,6 +127,8 @@ t.text "method_definition", default: "raise StandardError" t.text "email_subject" t.integer "redirect_type", default: 0 + t.jsonb "meta_data", default: {} + t.integer "parent_id" t.index ["api_namespace_id"], name: "index_api_actions_on_api_namespace_id" t.index ["api_resource_id"], name: "index_api_actions_on_api_resource_id" end diff --git a/test/controllers/resource_controller_test.rb b/test/controllers/resource_controller_test.rb index 8afd7fd08..3409c37bc 100644 --- a/test/controllers/resource_controller_test.rb +++ b/test/controllers/resource_controller_test.rb @@ -941,4 +941,92 @@ class ResourceControllerTest < ActionDispatch::IntegrationTest # When user is signed in assert_equal Ahoy::Event.last.properties['user_id'], user.id end + + test 'should run error api action if recaptcha verification failed' do + @api_namespace.api_form.update(show_recaptcha: true) + payload = { + data: { + properties: { + first_name: 'Don' + } + } + } + # Recaptcha is disabled for test env by deafult + Recaptcha.configuration.skip_verify_env.delete("test") + assert_no_difference "@api_namespace.api_resources.count" do + assert_difference "@api_namespace.reload.executed_api_actions.count", +@api_namespace.error_api_actions.count do + post api_namespace_resource_index_url(api_namespace_id: @api_namespace.id), params: payload + assert_response :success + end + end + + # should store user input with proper error message + expected_json = {"api_resource"=>{"errors"=>"reCAPTCHA verification failed, please try again.", "properties"=>{"first_name"=>"Don"}, "api_namespace_id"=>@api_namespace.id}} + assert_equal expected_json, @api_namespace.executed_api_actions.order(:created_at).last.meta_data + + Recaptcha.configuration.skip_verify_env.push("test") + end + + test 'should run error api action if there are any error while saving api resources' do + @api_namespace.api_form.update(properties: { 'name': {'label': 'Test', 'placeholder': 'Test', 'field_type': 'input', 'required': '1' }}) + payload = { + data: { + properties: { + name: '', + } + } + } + assert_no_difference "@api_namespace.api_resources.count" do + assert_difference "@api_namespace.reload.executed_api_actions.count", +@api_namespace.error_api_actions.count do + post api_namespace_resource_index_url(api_namespace_id: @api_namespace.id), params: payload + end + end + + # should store user input with proper error message + expected_json = {"api_resource"=>{"errors"=>"Properties name is required", "properties"=>{"name"=>""}, "api_namespace_id"=>@api_namespace.id}} + assert_equal expected_json, @api_namespace.executed_api_actions.order(:created_at).last.meta_data + end + + test 'should run all error api actions once for each failed api actions' do + api_namespace = api_namespaces(:no_api_actions) + # this api action will raise an error on execution + CreateApiAction.create(api_namespace_id: api_namespace.id, action_type: 'custom_action', method_definition: "raise StandardError") + # This api action should execute incase of any errors + ErrorApiAction.create(api_namespace_id: api_namespace.id, action_type: 'custom_action', method_definition: "p 'no errors here'") + + payload = { + data: { + properties: { + name: 'test', + } + } + } + + Sidekiq::Testing.inline! do + assert_difference "api_namespace.api_resources.count", +1 do + assert_difference "api_namespace.reload.executed_api_actions.where(type: 'ErrorApiAction').count", +1 do + perform_enqueued_jobs do + assert_raises StandardError do + post api_namespace_resource_index_url(api_namespace_id: api_namespace.id), params: payload + end + end + end + end + end + + assert_equal ["\"no errors here\""], api_namespace.executed_api_actions.where(type: 'ErrorApiAction').pluck(:lifecycle_message) + + # This api action should execute incase of any errors + ErrorApiAction.create(api_namespace_id: api_namespace.id, action_type: 'custom_action', method_definition: "p 'no error here either'") + + Sidekiq::Testing.inline! do + assert_difference "api_namespace.reload.executed_api_actions.where(type: 'ErrorApiAction').count", +2 do + perform_enqueued_jobs do + assert_raises StandardError do + post api_namespace_resource_index_url(api_namespace_id: api_namespace.id), params: payload + end + end + end + end + end end diff --git a/test/controllers/simple_discussion/forum_threads_controller_test.rb b/test/controllers/simple_discussion/forum_threads_controller_test.rb index 55ea8110d..5f0ae2b98 100644 --- a/test/controllers/simple_discussion/forum_threads_controller_test.rb +++ b/test/controllers/simple_discussion/forum_threads_controller_test.rb @@ -261,14 +261,14 @@ class SimpleDiscussion::ForumThreadsControllerTest < ActionDispatch::Integration } } } - post simple_discussion.forum_threads_url, params: payload + assert_difference "ApiResource.count", +2 do + post simple_discussion.forum_threads_url, params: payload post simple_discussion.forum_thread_forum_posts_path(ForumThread.last), params: { forum_post: { body: "Reply" } } - Sidekiq::Worker.drain_all end end diff --git a/test/fixtures/api_actions.yml b/test/fixtures/api_actions.yml index 2d22b77bc..a05ec9b31 100644 --- a/test/fixtures/api_actions.yml +++ b/test/fixtures/api_actions.yml @@ -94,11 +94,12 @@ create_api_action_plugin_subdomain_events: action_type: send_web_request include_api_resource_data: true payload_mapping: { - "content": "#{self.representation.body}" + "content": "#{api_resource.properties&.dig('representation', 'body')}" } request_url: "http://www.example.com/success" api_namespace: plugin_subdomain_events http_method: post + custom_headers: {"AUTHORIZATION":"SECRET_BEARER_TOKEN"} create_custom_api_action_three: type: CreateApiAction @@ -112,11 +113,12 @@ create_api_action_plugin_bishop_monitoring_web_request: type: CreateApiAction action_type: send_web_request payload_mapping: { - "content": "#{self.representation.body}" + "content": "#{api_resource.properties&.dig('representation', 'body')}" } request_url: "http://www.discord.com" api_namespace: monitoring_target_incident http_method: post + custom_headers: {"AUTHORIZATION":"SECRET_BEARER_TOKEN"} create_api_action_plugin_bishop_monitoring_email: type: CreateApiAction diff --git a/test/helpers/content_helper_test.rb b/test/helpers/content_helper_test.rb index e00bc16a7..d57b21cd6 100644 --- a/test/helpers/content_helper_test.rb +++ b/test/helpers/content_helper_test.rb @@ -135,7 +135,7 @@ class ContentHelperTest < ActionView::TestCase @current_user = @user params[:properties] = {name: { value: 'test user', option: 'PARTIAL' }}.to_json - snippet = Comfy::Cms::Snippet.create(site_id: @cms_site.id, label: 'clients', identifier: @api_namespace.slug, position: 0, content: "<% @api_resources.each do |res| %><%= res.properties['name'] %><% end %>") + snippet = Comfy::Cms::Snippet.create(site_id: @cms_site.id, label: 'clients', identifier: @api_namespace.slug, position: 0, content: "<% @api_resources.jsonb_order_pre({ properties: { name: 'ASC'}}).each do |res| %><%= res.properties['name'] %><% end %>") response = render_api_namespace_resource_index(@api_namespace.slug, { 'scope' => { 'current_user' => 'true' } }) excepted_response = "#{@api_resource_1.properties['name']}#{@api_resource_2.properties['name']}" diff --git a/test/models/api_action_test.rb b/test/models/api_action_test.rb index 65becda16..e51d70239 100644 --- a/test/models/api_action_test.rb +++ b/test/models/api_action_test.rb @@ -80,4 +80,53 @@ class ApiActionTest < ActiveSupport::TestCase end end end + + test 'should update incomplete api actions when parent action is updated' do + api_namespace_action = api_actions(:create_api_action_one) + api_resource = api_resources(:one) + api_resource_action = api_resource.create_api_actions.create(api_namespace_action.attributes.merge('lifecycle_stage' => 'initialized', parent_id: api_namespace_action.id).except("id", "created_at", "updated_at", "api_namespace_id")) + + assert_equal api_namespace_action.email, api_resource_action.email + + api_namespace_action.update(email: 'test@random.com') + + assert_equal 'test@random.com', api_resource_action.reload.email + end + + test 'should not update completed api actions when parent action is updated' do + api_namespace_action = api_actions(:create_api_action_one) + api_resource = api_resources(:one) + api_resource_action = api_resource.create_api_actions.create(api_namespace_action.attributes.merge('lifecycle_stage' => 'complete', parent_id: api_namespace_action.id).except("id", "created_at", "updated_at", "api_namespace_id")) + + assert_equal api_namespace_action.email, api_resource_action.email + + api_namespace_action.update(email: 'test@random.com') + + refute_equal 'test@random.com', api_resource_action.reload.email + end + + test "should rerun with new code when api action is updated" do + api_namespace = api_namespaces(:one) + + custom_action = api_actions(:create_api_action_one).dup + custom_action.action_type = "custom_action" + custom_action.method_definition = "1'+2" + custom_action.save! + + assert_difference 'CreateApiAction.count', +api_namespace.reload.create_api_actions.count do + @api_resource = ApiResource.create!(api_namespace_id: api_namespace.id, properties: {'name': 'John Doe'}) + end + + api_action = @api_resource.create_api_actions.find_by(action_type: 'custom_action') + + assert_raises SyntaxError do + api_action.reload.execute_action(true) + end + + custom_action.update(method_definition: "1+2") + + assert_changes -> { api_action.reload.lifecycle_stage }, to: 'complete' do + api_action.reload.execute_action(true) + end + end end diff --git a/test/models/api_namespace_test.rb b/test/models/api_namespace_test.rb index dd9e69c43..f9f966629 100755 --- a/test/models/api_namespace_test.rb +++ b/test/models/api_namespace_test.rb @@ -33,7 +33,18 @@ class ApiNamespaceTest < ActiveSupport::TestCase service.track_event Sidekiq::Worker.drain_all end - assert_equal @subdomain_events_api.executed_api_actions.first.reload.lifecycle_stage, 'failed' + + assert_equal @subdomain_events_api.reload.executed_api_actions.first.lifecycle_stage, 'complete' + end + + test "plugin: subdomain/subdomain_events -> should run actions only once" do + CreateApiAction.any_instance.expects(:execute_action).times(@subdomain_events_api.api_actions.size) + + service = ApiNamespace::Plugin::V1::SubdomainEventsService.new(@message) + assert_difference "ApiResource.count", +1 do + service.track_event + Sidekiq::Worker.drain_all + end end test "should check the associated CMS entities: Page, Layout and Snippet for the api-namespace if the api-form snippet is content of them" do diff --git a/test/models/api_resource_test.rb b/test/models/api_resource_test.rb index 0239c8733..27af6e842 100755 --- a/test/models/api_resource_test.rb +++ b/test/models/api_resource_test.rb @@ -84,6 +84,8 @@ class ApiResourceTest < ActiveSupport::TestCase assert_difference 'ApiResource.count', +1 do assert_difference 'CreateApiAction.count', +api_namespace.reload.create_api_actions.count do @api_resource = ApiResource.create!(api_namespace_id: api_namespace.id, properties: {'name': 'John Doe'}) + # each model level api action should be executed individually + assert_equal api_namespace.create_api_actions.where(action_type: ApiAction::EXECUTION_ORDER[:model_level]).count, FireApiActionsJob.jobs.count Sidekiq::Worker.drain_all end end @@ -136,10 +138,13 @@ class ApiResourceTest < ActiveSupport::TestCase send_web_request_action.save! @api_resource = ApiResource.create!(api_namespace_id: api_namespace.id, properties: {'name': 'John Doe'}) + Sidekiq::Worker.drain_all perform_enqueued_jobs do assert_no_difference 'ApiResource.count' do assert_difference '@api_resource.reload.update_api_actions.count', +api_namespace.reload.update_api_actions.count do @api_resource.update!(properties: {'name': 'John Doe 2'}) + # each model level api action should be executed individually + assert_equal api_namespace.reload.update_api_actions.where(action_type: ApiAction::EXECUTION_ORDER[:model_level]).count, FireApiActionsJob.jobs.count Sidekiq::Worker.drain_all end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 20d7086c4..539f080d5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -32,6 +32,7 @@ class ActiveSupport::TestCase end stub_request(:post, "www.example.com/success").to_return(body: "success response", status: 200) stub_request(:post, "www.example.com/error").to_return(body: "error response", status: 500) + stub_request(:post, "http://www.discord.com/").to_return(body: "success response", status: 200) end teardown do