From 7cfef77916b175fd7222f17e77b4d0359602f2ee Mon Sep 17 00:00:00 2001 From: maakle Date: Fri, 20 Feb 2026 15:05:30 +0100 Subject: [PATCH 1/2] refactor: update job retrieval logic and improve error handling tests - Changed job retrieval from `each { |_page| break }` to `each.first` for better clarity and efficiency in `basic_behavior_spec.rb`. - Updated request method assertion from `method` to `http_method` in `employee_form_flow_spec.rb` for consistency. - Introduced a `capture_error` method in `error_handling_spec.rb` to streamline error handling tests and improve readability. - Adjusted comments in `job_board_spec.rb` for clarity. - Updated `CapturedRequest` struct to use `http_method` instead of `method` in `test_context.rb` for better semantic accuracy. --- spec/basic_behavior_spec.rb | 11 ++- spec/employee_form_flow_spec.rb | 3 +- spec/error_handling_spec.rb | 132 ++++++++++++++++---------------- spec/job_board_spec.rb | 3 +- spec/spec_helper.rb | 1 - spec/support/test_context.rb | 21 +++-- 6 files changed, 83 insertions(+), 88 deletions(-) diff --git a/spec/basic_behavior_spec.rb b/spec/basic_behavior_spec.rb index cca62d78..b0b5f103 100644 --- a/spec/basic_behavior_spec.rb +++ b/spec/basic_behavior_spec.rb @@ -21,7 +21,7 @@ ) jobs = ctx.kombo.ats.get_jobs - jobs.each { |_page| break } + jobs.each.first request = ctx.get_last_request expect(request.headers['Authorization']).to eq('Bearer my-custom-api-key') @@ -45,7 +45,7 @@ ) jobs = ctx.kombo.ats.get_jobs - jobs.each { |_page| break } + jobs.each.first request = ctx.get_last_request expect(request.headers['X-Integration-Id']).to eq('my-integration-123') @@ -69,7 +69,7 @@ ) jobs = ctx.kombo.ats.get_jobs - jobs.each { |_page| break } + jobs.each.first request = ctx.get_last_request # When integration ID is undefined, the header is not set @@ -126,7 +126,7 @@ # Test with boolean true jobs_with_deleted = ctx.kombo.ats.get_jobs(include_deleted: true) - jobs_with_deleted.each { |_page| break } + jobs_with_deleted.each.first request_with_deleted = ctx.get_last_request expect(request_with_deleted.path).to include('include_deleted=true') @@ -146,7 +146,7 @@ # Test with boolean false jobs_without_deleted = ctx.kombo.ats.get_jobs(include_deleted: false) - jobs_without_deleted.each { |_page| break } + jobs_without_deleted.each.first request_without_deleted = ctx.get_last_request expect(request_without_deleted.path).to include('include_deleted=false') @@ -530,4 +530,3 @@ end end end - diff --git a/spec/employee_form_flow_spec.rb b/spec/employee_form_flow_spec.rb index d4c9df28..5fb60708 100644 --- a/spec/employee_form_flow_spec.rb +++ b/spec/employee_form_flow_spec.rb @@ -163,7 +163,7 @@ # Verify request body is correctly serialized request = ctx.get_last_request - expect(request.method).to eq('POST') + expect(request.http_method).to eq('POST') expect(request.body).to be_a(Hash) expect(request.body['properties']).to be_truthy expect(request.body['properties']['firstName']).to eq('John') @@ -174,4 +174,3 @@ expect(request.body['properties']['workLocation']['site']).to eq('FXrER44xubBqA9DLgZ3PFNNx') end end - diff --git a/spec/error_handling_spec.rb b/spec/error_handling_spec.rb index c99a43a2..69cd08d0 100644 --- a/spec/error_handling_spec.rb +++ b/spec/error_handling_spec.rb @@ -6,6 +6,17 @@ TestSupport.describe_sdk_suite 'Error Handling' do include TestSupport + def capture_error(error_class) + captured_error = nil + expect do + yield + rescue error_class => e + captured_error = e + raise + end.to raise_error(error_class) + captured_error + end + describe 'ATS endpoints' do it 'returns KomboAtsError for platform rate limit errors' do ctx = TestSupport::TestContext.new @@ -27,17 +38,16 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::KomboAtsError) do jobs = ctx.kombo.ats.get_jobs - jobs.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::KomboAtsError) do |error| - expect(error.error.message).to include('You have exceeded the rate limit. Please try again later.') - expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) - expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_RATE_LIMIT_EXCEEDED) - expect(error.error.title).to eq('Rate limit exceeded') - expect(error.error.message).to eq('You have exceeded the rate limit. Please try again later.') - expect(error.error.log_url).to eq('https://app.kombo.dev/logs/abc123') + jobs.each.first end + expect(error.error.message).to include('You have exceeded the rate limit. Please try again later.') + expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) + expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_RATE_LIMIT_EXCEEDED) + expect(error.error.title).to eq('Rate limit exceeded') + expect(error.error.message).to eq('You have exceeded the rate limit. Please try again later.') + expect(error.error.log_url).to eq('https://app.kombo.dev/logs/abc123') end it 'returns KomboAtsError for ATS-specific job closed errors' do @@ -69,16 +79,15 @@ candidate: candidate ) - expect do + error = capture_error(Kombo::Models::Errors::KomboAtsError) do ctx.kombo.ats.create_application(job_id: 'test-job-id', body: body) - end.to raise_error(Kombo::Models::Errors::KomboAtsError) do |error| - expect(error.error.message).to include('Cannot create application for a closed job. The job must be in an open state.') - expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) - expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::ATS_JOB_CLOSED) - expect(error.error.title).to eq('Job is closed') - expect(error.error.message).to eq('Cannot create application for a closed job. The job must be in an open state.') - expect(error.error.log_url).to eq('https://app.kombo.dev/logs/ghi789') end + expect(error.error.message).to include('Cannot create application for a closed job. The job must be in an open state.') + expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) + expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::ATS_JOB_CLOSED) + expect(error.error.title).to eq('Job is closed') + expect(error.error.message).to eq('Cannot create application for a closed job. The job must be in an open state.') + expect(error.error.log_url).to eq('https://app.kombo.dev/logs/ghi789') end end @@ -103,17 +112,16 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::KomboHrisError) do employees = ctx.kombo.hris.get_employees - employees.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::KomboHrisError) do |error| - expect(error.error.message).to include('The integration is missing required permissions to access this resource.') - expect(error.status).to eq(Kombo::Models::Shared::KomboHrisErrorStatus::ERROR) - expect(error.error.code).to eq(Kombo::Models::Shared::KomboHrisErrorCode::INTEGRATION_PERMISSION_MISSING) - expect(error.error.title).to eq('Permission missing') - expect(error.error.message).to eq('The integration is missing required permissions to access this resource.') - expect(error.error.log_url).to eq('https://app.kombo.dev/logs/hris-def456') + employees.each.first end + expect(error.error.message).to include('The integration is missing required permissions to access this resource.') + expect(error.status).to eq(Kombo::Models::Shared::KomboHrisErrorStatus::ERROR) + expect(error.error.code).to eq(Kombo::Models::Shared::KomboHrisErrorCode::INTEGRATION_PERMISSION_MISSING) + expect(error.error.title).to eq('Permission missing') + expect(error.error.message).to eq('The integration is missing required permissions to access this resource.') + expect(error.error.log_url).to eq('https://app.kombo.dev/logs/hris-def456') end end @@ -138,17 +146,16 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::KomboAtsError) do orders = ctx.kombo.assessment.get_open_orders - orders.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::KomboAtsError) do |error| - expect(error.error.message).to include('The provided input is invalid or malformed.') - expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) - expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_INPUT_INVALID) - expect(error.error.title).to eq('Input invalid') - expect(error.error.message).to eq('The provided input is invalid or malformed.') - expect(error.error.log_url).to eq('https://app.kombo.dev/logs/assessment-xyz') + orders.each.first end + expect(error.error.message).to include('The provided input is invalid or malformed.') + expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) + expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_INPUT_INVALID) + expect(error.error.title).to eq('Input invalid') + expect(error.error.message).to eq('The provided input is invalid or malformed.') + expect(error.error.log_url).to eq('https://app.kombo.dev/logs/assessment-xyz') end end @@ -173,16 +180,15 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::KomboGeneralError) do ctx.kombo.general.check_api_key - end.to raise_error(Kombo::Models::Errors::KomboGeneralError) do |error| - expect(error.error.message).to include('The provided API key is invalid or expired.') - expect(error.status).to eq(Kombo::Models::Shared::KomboGeneralErrorStatus::ERROR) - expect(error.error.code).to eq(Kombo::Models::Shared::KomboGeneralErrorCode::PLATFORM_AUTHENTICATION_INVALID) - expect(error.error.title).to eq('Authentication invalid') - expect(error.error.message).to eq('The provided API key is invalid or expired.') - expect(error.error.log_url).to eq('https://app.kombo.dev/logs/general-auth-123') end + expect(error.error.message).to include('The provided API key is invalid or expired.') + expect(error.status).to eq(Kombo::Models::Shared::KomboGeneralErrorStatus::ERROR) + expect(error.error.code).to eq(Kombo::Models::Shared::KomboGeneralErrorCode::PLATFORM_AUTHENTICATION_INVALID) + expect(error.error.title).to eq('Authentication invalid') + expect(error.error.message).to eq('The provided API key is invalid or expired.') + expect(error.error.log_url).to eq('https://app.kombo.dev/logs/general-auth-123') end end @@ -201,13 +207,12 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::APIError) do jobs = ctx.kombo.ats.get_jobs - jobs.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::APIError) do |error| - expect(error.status_code).to eq(500) - expect(error.body).to include('500 Internal Server Error') + jobs.each.first end + expect(error.status_code).to eq(500) + expect(error.body).to include('500 Internal Server Error') end it 'handles plain text 502 bad gateway error' do @@ -225,13 +230,12 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::APIError) do employees = ctx.kombo.hris.get_employees - employees.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::APIError) do |error| - expect(error.status_code).to eq(502) - expect(error.body).to include('502 Bad Gateway') + employees.each.first end + expect(error.status_code).to eq(502) + expect(error.body).to include('502 Bad Gateway') end it 'handles HTML error page from nginx' do @@ -267,12 +271,11 @@ candidate: candidate ) - expect do + error = capture_error(Kombo::Models::Errors::APIError) do ctx.kombo.ats.create_application(job_id: 'test-job-id', body: body) - end.to raise_error(Kombo::Models::Errors::APIError) do |error| - expect(error.status_code).to eq(503) - expect(error.body).to include('503 Service Temporarily Unavailable') end + expect(error.status_code).to eq(503) + expect(error.body).to include('503 Service Temporarily Unavailable') end it 'handles empty response body with error status code' do @@ -288,11 +291,10 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::APIError) do ctx.kombo.general.check_api_key - end.to raise_error(Kombo::Models::Errors::APIError) do |error| - expect(error.status_code).to eq(500) end + expect(error.status_code).to eq(500) end it 'handles unexpected Content-Type header' do @@ -311,15 +313,13 @@ } ) - expect do + error = capture_error(Kombo::Models::Errors::APIError) do applications = ctx.kombo.ats.get_applications - applications.each { |_page| break } - end.to raise_error(Kombo::Models::Errors::APIError) do |error| - expect(error.status_code).to eq(500) - expect(error.body).to include('Server error occurred') + applications.each.first end + expect(error.status_code).to eq(500) + expect(error.body).to include('Server error occurred') end end end end - diff --git a/spec/job_board_spec.rb b/spec/job_board_spec.rb index c873e425..80c0cd27 100644 --- a/spec/job_board_spec.rb +++ b/spec/job_board_spec.rb @@ -31,8 +31,7 @@ request = ctx.get_last_request # The path should include the base path expect(request.path).to include('/v1/ats/jobs') - # Note: Default query parameters may or may not be included in the URL + # NOTE: Default query parameters may or may not be included in the URL # depending on how the query parameter serialization handles default values end end - diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index dd6bbbb7..70ab82e9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -28,4 +28,3 @@ WebMock.allow_net_connect! end end - diff --git a/spec/support/test_context.rb b/spec/support/test_context.rb index 92d71421..0a3f7d31 100644 --- a/spec/support/test_context.rb +++ b/spec/support/test_context.rb @@ -5,7 +5,7 @@ require 'kombo' module TestSupport - CapturedRequest = Struct.new(:method, :path, :headers, :body, keyword_init: true) + CapturedRequest = Struct.new(:http_method, :path, :headers, :body, keyword_init: true) class TestContext attr_reader :kombo @@ -50,14 +50,14 @@ def initialize(api_key: 'test-api-key', integration_id: 'test-integration-id') uri_str = uri.to_s path_start = uri_str.index(uri.path) if path_start - full_path = uri_str[path_start..-1] + full_path = uri_str[path_start..] # Remove fragment if present full_path = full_path.split('#').first end end captured = CapturedRequest.new( - method: request_signature.method.to_s.upcase, + http_method: request_signature.method.to_s.upcase, path: full_path, headers: headers_hash, body: body @@ -83,18 +83,18 @@ def mock_endpoint(method:, path:, response:) # will be matched sequentially. Use times(1) to ensure each stub only matches once. url_pattern = if method == 'GET' # Use regex to match path with any query params - /^https:\/\/api\.kombo\.dev#{Regexp.escape(path)}(\?.*)?$/ + %r{^https://api\.kombo\.dev#{Regexp.escape(path)}(\?.*)?$} else "https://api.kombo.dev#{path}" end stub = WebMock.stub_request(method.downcase.to_sym, url_pattern) - .to_return( - status: status_code, - body: body.to_json, - headers: headers - ) - .times(1) + .to_return( + status: status_code, + body: body.to_json, + headers: headers + ) + .times(1) @stubs << stub end @@ -138,4 +138,3 @@ def self.describe_sdk_suite(name, &block) end end end - From cb497127d0dee9e3ac15363aaf36b4e56fde5089 Mon Sep 17 00:00:00 2001 From: maakle Date: Fri, 20 Feb 2026 15:26:35 +0100 Subject: [PATCH 2/2] refactor: streamline job retrieval and enhance error handling assertions - Simplified job retrieval in `basic_behavior_spec.rb` by removing unnecessary variable assignments. - Improved error handling tests in `error_handling_spec.rb` by replacing the `capture_error` method with inline error handling for better clarity and consistency. --- spec/basic_behavior_spec.rb | 15 ++--- spec/error_handling_spec.rb | 109 ++++++++++++++++++++++-------------- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/spec/basic_behavior_spec.rb b/spec/basic_behavior_spec.rb index b0b5f103..f2e173ee 100644 --- a/spec/basic_behavior_spec.rb +++ b/spec/basic_behavior_spec.rb @@ -20,8 +20,7 @@ } ) - jobs = ctx.kombo.ats.get_jobs - jobs.each.first + ctx.kombo.ats.get_jobs request = ctx.get_last_request expect(request.headers['Authorization']).to eq('Bearer my-custom-api-key') @@ -44,8 +43,7 @@ } ) - jobs = ctx.kombo.ats.get_jobs - jobs.each.first + ctx.kombo.ats.get_jobs request = ctx.get_last_request expect(request.headers['X-Integration-Id']).to eq('my-integration-123') @@ -68,8 +66,7 @@ } ) - jobs = ctx.kombo.ats.get_jobs - jobs.each.first + ctx.kombo.ats.get_jobs request = ctx.get_last_request # When integration ID is undefined, the header is not set @@ -125,8 +122,7 @@ ) # Test with boolean true - jobs_with_deleted = ctx.kombo.ats.get_jobs(include_deleted: true) - jobs_with_deleted.each.first + ctx.kombo.ats.get_jobs(include_deleted: true) request_with_deleted = ctx.get_last_request expect(request_with_deleted.path).to include('include_deleted=true') @@ -145,8 +141,7 @@ ) # Test with boolean false - jobs_without_deleted = ctx.kombo.ats.get_jobs(include_deleted: false) - jobs_without_deleted.each.first + ctx.kombo.ats.get_jobs(include_deleted: false) request_without_deleted = ctx.get_last_request expect(request_without_deleted.path).to include('include_deleted=false') diff --git a/spec/error_handling_spec.rb b/spec/error_handling_spec.rb index 69cd08d0..e4050ca6 100644 --- a/spec/error_handling_spec.rb +++ b/spec/error_handling_spec.rb @@ -6,17 +6,6 @@ TestSupport.describe_sdk_suite 'Error Handling' do include TestSupport - def capture_error(error_class) - captured_error = nil - expect do - yield - rescue error_class => e - captured_error = e - raise - end.to raise_error(error_class) - captured_error - end - describe 'ATS endpoints' do it 'returns KomboAtsError for platform rate limit errors' do ctx = TestSupport::TestContext.new @@ -38,10 +27,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::KomboAtsError) do - jobs = ctx.kombo.ats.get_jobs - jobs.each.first - end + error = nil + expect do + ctx.kombo.ats.get_jobs + rescue Kombo::Models::Errors::KomboAtsError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::KomboAtsError) expect(error.error.message).to include('You have exceeded the rate limit. Please try again later.') expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_RATE_LIMIT_EXCEEDED) @@ -79,9 +71,13 @@ def capture_error(error_class) candidate: candidate ) - error = capture_error(Kombo::Models::Errors::KomboAtsError) do + error = nil + expect do ctx.kombo.ats.create_application(job_id: 'test-job-id', body: body) - end + rescue Kombo::Models::Errors::KomboAtsError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::KomboAtsError) expect(error.error.message).to include('Cannot create application for a closed job. The job must be in an open state.') expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::ATS_JOB_CLOSED) @@ -112,10 +108,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::KomboHrisError) do - employees = ctx.kombo.hris.get_employees - employees.each.first - end + error = nil + expect do + ctx.kombo.hris.get_employees + rescue Kombo::Models::Errors::KomboHrisError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::KomboHrisError) expect(error.error.message).to include('The integration is missing required permissions to access this resource.') expect(error.status).to eq(Kombo::Models::Shared::KomboHrisErrorStatus::ERROR) expect(error.error.code).to eq(Kombo::Models::Shared::KomboHrisErrorCode::INTEGRATION_PERMISSION_MISSING) @@ -146,10 +145,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::KomboAtsError) do - orders = ctx.kombo.assessment.get_open_orders - orders.each.first - end + error = nil + expect do + ctx.kombo.assessment.get_open_orders + rescue Kombo::Models::Errors::KomboAtsError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::KomboAtsError) expect(error.error.message).to include('The provided input is invalid or malformed.') expect(error.status).to eq(Kombo::Models::Shared::KomboAtsErrorStatus::ERROR) expect(error.error.code).to eq(Kombo::Models::Shared::KomboAtsErrorCode::PLATFORM_INPUT_INVALID) @@ -180,9 +182,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::KomboGeneralError) do + error = nil + expect do ctx.kombo.general.check_api_key - end + rescue Kombo::Models::Errors::KomboGeneralError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::KomboGeneralError) expect(error.error.message).to include('The provided API key is invalid or expired.') expect(error.status).to eq(Kombo::Models::Shared::KomboGeneralErrorStatus::ERROR) expect(error.error.code).to eq(Kombo::Models::Shared::KomboGeneralErrorCode::PLATFORM_AUTHENTICATION_INVALID) @@ -207,10 +213,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::APIError) do - jobs = ctx.kombo.ats.get_jobs - jobs.each.first - end + error = nil + expect do + ctx.kombo.ats.get_jobs + rescue Kombo::Models::Errors::APIError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::APIError) expect(error.status_code).to eq(500) expect(error.body).to include('500 Internal Server Error') end @@ -230,10 +239,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::APIError) do - employees = ctx.kombo.hris.get_employees - employees.each.first - end + error = nil + expect do + ctx.kombo.hris.get_employees + rescue Kombo::Models::Errors::APIError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::APIError) expect(error.status_code).to eq(502) expect(error.body).to include('502 Bad Gateway') end @@ -271,9 +283,13 @@ def capture_error(error_class) candidate: candidate ) - error = capture_error(Kombo::Models::Errors::APIError) do + error = nil + expect do ctx.kombo.ats.create_application(job_id: 'test-job-id', body: body) - end + rescue Kombo::Models::Errors::APIError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::APIError) expect(error.status_code).to eq(503) expect(error.body).to include('503 Service Temporarily Unavailable') end @@ -291,9 +307,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::APIError) do + error = nil + expect do ctx.kombo.general.check_api_key - end + rescue Kombo::Models::Errors::APIError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::APIError) expect(error.status_code).to eq(500) end @@ -313,10 +333,13 @@ def capture_error(error_class) } ) - error = capture_error(Kombo::Models::Errors::APIError) do - applications = ctx.kombo.ats.get_applications - applications.each.first - end + error = nil + expect do + ctx.kombo.ats.get_applications + rescue Kombo::Models::Errors::APIError => e + error = e + raise + end.to raise_error(Kombo::Models::Errors::APIError) expect(error.status_code).to eq(500) expect(error.body).to include('Server error occurred') end