Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions spec/basic_behavior_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
}
)

jobs = ctx.kombo.ats.get_jobs
jobs.each { |_page| break }
ctx.kombo.ats.get_jobs

request = ctx.get_last_request
expect(request.headers['Authorization']).to eq('Bearer my-custom-api-key')
Expand All @@ -44,8 +43,7 @@
}
)

jobs = ctx.kombo.ats.get_jobs
jobs.each { |_page| break }
ctx.kombo.ats.get_jobs

request = ctx.get_last_request
expect(request.headers['X-Integration-Id']).to eq('my-integration-123')
Expand All @@ -68,8 +66,7 @@
}
)

jobs = ctx.kombo.ats.get_jobs
jobs.each { |_page| break }
ctx.kombo.ats.get_jobs

request = ctx.get_last_request
# When integration ID is undefined, the header is not set
Expand Down Expand Up @@ -125,8 +122,7 @@
)

# Test with boolean true
jobs_with_deleted = ctx.kombo.ats.get_jobs(include_deleted: true)
jobs_with_deleted.each { |_page| break }
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')
Expand All @@ -145,8 +141,7 @@
)

# Test with boolean false
jobs_without_deleted = ctx.kombo.ats.get_jobs(include_deleted: false)
jobs_without_deleted.each { |_page| break }
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')
Expand Down Expand Up @@ -530,4 +525,3 @@
end
end
end

3 changes: 1 addition & 2 deletions spec/employee_form_flow_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -174,4 +174,3 @@
expect(request.body['properties']['workLocation']['site']).to eq('FXrER44xubBqA9DLgZ3PFNNx')
end
end

167 changes: 95 additions & 72 deletions spec/error_handling_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@
}
)

error = nil
expect 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')
end
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)
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
Expand Down Expand Up @@ -69,16 +71,19 @@
candidate: candidate
)

error = nil
expect 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
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)
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

Expand All @@ -103,17 +108,19 @@
}
)

error = nil
expect 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')
end
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)
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

Expand All @@ -138,17 +145,19 @@
}
)

error = nil
expect 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')
end
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)
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

Expand All @@ -173,16 +182,19 @@
}
)

error = nil
expect 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
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)
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

Expand All @@ -201,13 +213,15 @@
}
)

error = nil
expect 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')
end
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

it 'handles plain text 502 bad gateway error' do
Expand All @@ -225,13 +239,15 @@
}
)

error = nil
expect 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')
end
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

it 'handles HTML error page from nginx' do
Expand Down Expand Up @@ -267,12 +283,15 @@
candidate: candidate
)

error = nil
expect 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
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

it 'handles empty response body with error status code' do
Expand All @@ -288,11 +307,14 @@
}
)

error = nil
expect 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
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

it 'handles unexpected Content-Type header' do
Expand All @@ -311,15 +333,16 @@
}
)

error = nil
expect 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')
end
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
end
end
end

3 changes: 1 addition & 2 deletions spec/job_board_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

1 change: 0 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,3 @@
WebMock.allow_net_connect!
end
end

21 changes: 10 additions & 11 deletions spec/support/test_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -138,4 +138,3 @@ def self.describe_sdk_suite(name, &block)
end
end
end