From 8debb3be2dc8576f2154db84d076de4b6e25cf30 Mon Sep 17 00:00:00 2001 From: Vishnu Narayanan Date: Thu, 31 Jul 2025 19:05:59 +0530 Subject: [PATCH] chore: add specs --- .../enterprise/cloudflare_cleanup_job_spec.rb | 53 +++++++++++++ .../delete_custom_hostname_service_spec.rb | 61 +++++++++++++++ .../list_custom_hostnames_service_spec.rb | 75 +++++++++++++++++++ spec/jobs/cloudflare_cleanup_job_spec.rb | 10 +++ 4 files changed, 199 insertions(+) create mode 100644 spec/enterprise/jobs/enterprise/cloudflare_cleanup_job_spec.rb create mode 100644 spec/enterprise/services/cloudflare/delete_custom_hostname_service_spec.rb create mode 100644 spec/enterprise/services/cloudflare/list_custom_hostnames_service_spec.rb create mode 100644 spec/jobs/cloudflare_cleanup_job_spec.rb diff --git a/spec/enterprise/jobs/enterprise/cloudflare_cleanup_job_spec.rb b/spec/enterprise/jobs/enterprise/cloudflare_cleanup_job_spec.rb new file mode 100644 index 000000000..7d465f511 --- /dev/null +++ b/spec/enterprise/jobs/enterprise/cloudflare_cleanup_job_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe CloudflareCleanupJob, type: :job do + let(:list_service) { instance_double(Cloudflare::ListCustomHostnamesService) } + let(:delete_service) { instance_double(Cloudflare::DeleteCustomHostnameService) } + + before do + allow(Cloudflare::ListCustomHostnamesService).to receive(:new).and_return(list_service) + allow(Cloudflare::DeleteCustomHostnameService).to receive(:new).and_return(delete_service) + end + + describe '#perform' do + context 'when not chatwoot cloud' do + it 'skips cleanup' do + allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(false) + + expect(list_service).not_to receive(:perform) + described_class.perform_now + end + end + + context 'when chatwoot cloud' do + before do + allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true) + end + + it 'calls list service and processes results' do + allow(list_service).to receive(:perform).and_return({ data: [] }) + + described_class.perform_now + + expect(list_service).to have_received(:perform) + end + + it 'calls delete service for orphaned hostnames' do + create(:portal, custom_domain: 'existing.com') + hostnames = [ + { 'id' => 'keep-id', 'hostname' => 'existing.com' }, + { 'id' => 'delete-id', 'hostname' => 'orphaned.com' } + ] + + allow(list_service).to receive(:perform).and_return({ data: hostnames }) + allow(delete_service).to receive(:perform).and_return({ success: true }) + + described_class.perform_now + + expect(Cloudflare::DeleteCustomHostnameService).to have_received(:new) + .with(hostname_id: 'delete-id') + expect(delete_service).to have_received(:perform) + end + end + end +end diff --git a/spec/enterprise/services/cloudflare/delete_custom_hostname_service_spec.rb b/spec/enterprise/services/cloudflare/delete_custom_hostname_service_spec.rb new file mode 100644 index 000000000..762cc6041 --- /dev/null +++ b/spec/enterprise/services/cloudflare/delete_custom_hostname_service_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +RSpec.describe Cloudflare::DeleteCustomHostnameService do + let(:hostname_id) { 'hostname-id-123' } + let(:service) { described_class.new(hostname_id: hostname_id) } + + before do + create(:installation_config, name: 'CLOUDFLARE_API_KEY', value: 'test-api-token') + create(:installation_config, name: 'CLOUDFLARE_ZONE_ID', value: 'test-zone-id') + end + + describe '#perform' do + context 'when API token or zone ID is missing' do + it 'returns error when api_token is blank' do + InstallationConfig.find_by(name: 'CLOUDFLARE_API_KEY').update(value: '') + result = service.perform + expect(result[:errors]).to eq(['Cloudflare API token or zone ID not found']) + end + + it 'returns error when zone_id is blank' do + InstallationConfig.find_by(name: 'CLOUDFLARE_ZONE_ID').update(value: '') + result = service.perform + expect(result[:errors]).to eq(['Cloudflare API token or zone ID not found']) + end + end + + context 'when hostname_id is missing' do + it 'returns error' do + service_without_id = described_class.new(hostname_id: '') + result = service_without_id.perform + expect(result[:errors]).to eq(['Hostname ID is required']) + end + end + + context 'when API request succeeds' do + it 'returns success' do + stub_request(:delete, "https://api.cloudflare.com/client/v4/zones/test-zone-id/custom_hostnames/#{hostname_id}") + .to_return(status: 200, body: {}.to_json, headers: { 'Content-Type' => 'application/json' }) + + result = service.perform + + expect(result[:success]).to be_truthy + end + end + + context 'when API request fails' do + it 'returns API errors' do + error_response = { + 'errors' => [{ 'code' => 1234, 'message' => 'Hostname not found' }] + } + + stub_request(:delete, "https://api.cloudflare.com/client/v4/zones/test-zone-id/custom_hostnames/#{hostname_id}") + .to_return(status: 404, body: error_response.to_json, headers: { 'Content-Type' => 'application/json' }) + + result = service.perform + + expect(result[:errors]).to eq(error_response['errors']) + end + end + end +end diff --git a/spec/enterprise/services/cloudflare/list_custom_hostnames_service_spec.rb b/spec/enterprise/services/cloudflare/list_custom_hostnames_service_spec.rb new file mode 100644 index 000000000..b4cb948fb --- /dev/null +++ b/spec/enterprise/services/cloudflare/list_custom_hostnames_service_spec.rb @@ -0,0 +1,75 @@ +require 'rails_helper' + +RSpec.describe Cloudflare::ListCustomHostnamesService do + let(:service) { described_class.new } + + before do + create(:installation_config, name: 'CLOUDFLARE_API_KEY', value: 'test-api-token') + create(:installation_config, name: 'CLOUDFLARE_ZONE_ID', value: 'test-zone-id') + end + + describe '#perform' do + context 'when API token or zone ID is missing' do + it 'returns error when api_token is blank' do + InstallationConfig.find_by(name: 'CLOUDFLARE_API_KEY').update(value: '') + result = service.perform + expect(result[:errors]).to eq(['Cloudflare API token or zone ID not found']) + end + + it 'returns error when zone_id is blank' do + InstallationConfig.find_by(name: 'CLOUDFLARE_ZONE_ID').update(value: '') + result = service.perform + expect(result[:errors]).to eq(['Cloudflare API token or zone ID not found']) + end + end + + context 'when API request succeeds' do + let(:response_body) do + { + 'success' => true, + 'errors' => [], + 'messages' => [], + 'result' => [ + { + 'id' => '023e105f4ecef8ad9ca31a8372d0c353', + 'hostname' => 'app.example.com', + 'status' => 'active', + 'ssl' => { 'id' => '0d89c70d-ad9f-4843-b99f-6cc0252067e9', 'status' => 'active', 'method' => 'http', 'type' => 'dv' } + }, + { + 'id' => '124e205f4ecef8ad9ca31a8372d0c454', + 'hostname' => 'portal.example.com', + 'status' => 'pending', + 'ssl' => { 'id' => '1d89c70d-ad9f-4843-b99f-6cc0252067f0', 'status' => 'pending', 'method' => 'http', 'type' => 'dv' } + } + ], + 'result_info' => { 'page' => 1, 'per_page' => 20, 'count' => 2, 'total_count' => 2 } + } + end + + it 'returns custom hostnames data' do + stub_request(:get, 'https://api.cloudflare.com/client/v4/zones/test-zone-id/custom_hostnames') + .to_return(status: 200, body: response_body.to_json, headers: { 'Content-Type' => 'application/json' }) + + result = service.perform + + expect(result[:data]).to eq(response_body['result']) + end + end + + context 'when API request fails' do + it 'returns API errors' do + error_response = { + 'errors' => [{ 'code' => 1234, 'message' => 'API Error' }] + } + + stub_request(:get, 'https://api.cloudflare.com/client/v4/zones/test-zone-id/custom_hostnames') + .to_return(status: 400, body: error_response.to_json, headers: { 'Content-Type' => 'application/json' }) + + result = service.perform + + expect(result[:errors]).to eq(error_response['errors']) + end + end + end +end diff --git a/spec/jobs/cloudflare_cleanup_job_spec.rb b/spec/jobs/cloudflare_cleanup_job_spec.rb new file mode 100644 index 000000000..63756d0e7 --- /dev/null +++ b/spec/jobs/cloudflare_cleanup_job_spec.rb @@ -0,0 +1,10 @@ +require 'rails_helper' + +RSpec.describe CloudflareCleanupJob, type: :job do + subject(:job) { described_class.perform_later } + + it 'enqueues the job' do + expect { job }.to have_enqueued_job(described_class) + .on_queue('housekeeping') + end +end