Skip to content
Open
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
19 changes: 18 additions & 1 deletion lib/travis/api/app/endpoint/assembla.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
require 'travis/remote_vcs/repository'
require 'travis/api/v3/billing_client'
require 'travis/services/assembla_user_service'
require 'travis/services/assembla_notify_service'
require 'travis/remote_vcs/client'
require_relative '../jwt_utils'

class Travis::Api::App
Expand All @@ -13,6 +15,7 @@ class Assembla < Endpoint
include Travis::Api::App::JWTUtils

REQUIRED_JWT_FIELDS = %w[name email login space_id repository_id id refresh_token].freeze
REQUIRED_NOTIFY_FIELDS = %w[action object id].freeze
CLUSTER_HEADER = 'HTTP_X_ASSEMBLA_CLUSTER'.freeze

set prefix: '/assembla'
Expand All @@ -39,6 +42,19 @@ class Assembla < Endpoint
}
end

post '/notify' do
service = Travis::Services::AssemblaNotifyService.new(@jwt_payload)
if service.run
{
status: 200,
body: { message: 'Assembla notification processed successfully' }
}
else
Travis.logger.error("Failed to process Assembla notification")
halt 500, { error: 'Failed to process notification' }
end
end

private

def validate_request!
Expand All @@ -49,7 +65,8 @@ def validate_request!
end

def check_required_fields
missing = REQUIRED_JWT_FIELDS.select { |f| @jwt_payload[f].nil? || @jwt_payload[f].to_s.strip.empty? }
required_fields = request.path_info.end_with?('/notify') ? REQUIRED_NOTIFY_FIELDS : REQUIRED_JWT_FIELDS
missing = required_fields.select { |f| @jwt_payload[f].nil? || @jwt_payload[f].to_s.strip.empty? }
unless missing.empty?
halt 400, { error: 'Missing required fields', missing: missing }
end
Expand Down
18 changes: 18 additions & 0 deletions lib/travis/remote_vcs/organization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

require 'travis/remote_vcs/client'

module Travis
class RemoteVCS
class Organization < Client
def destroy(org_id:)
request(:delete, __method__, false) do |req|
req.url "organizations/#{org_id}"
end
rescue ResponseError => e
Travis.logger.error("Failed to destroy organization: #{e.message}")
false
end
end
end
end
9 changes: 9 additions & 0 deletions lib/travis/remote_vcs/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ def set_perforce_ticket(repository_id:, user_id:)
rescue ResponseError
{}
end

def destroy(repository_id:)
request(:delete, __method__, false) do |req|
req.url "repos/#{repository_id}"
end
rescue ResponseError => e
Travis.logger.error("Failed to destroy repository: #{e.message}")
false
end
end
end
end
53 changes: 53 additions & 0 deletions lib/travis/services/assembla_notify_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

require 'travis/remote_vcs/repository'
require 'travis/remote_vcs/organization'

module Travis
module Services
class AssemblaNotifyService
VALID_ACTIONS = %w[destroy].freeze
VALID_OBJECTS = %w[space tool].freeze

def initialize(payload)
@action = payload[:action]
@object = payload[:object]
@object_id = payload[:id]
end

def run
validate
case @object
when 'tool'
handle_tool_destruction
when 'space'
handle_space_destruction
else
{ status: 400, body: { error: 'Unsupported object type for destruction' } }
end
end

private

def validate
unless VALID_ACTIONS.include?(@action)
return { status: 400, body: { error: 'Invalid action', allowed_actions: VALID_ACTIONS } }
end

unless VALID_OBJECTS.include?(@object)
return { status: 400, body: { error: 'Invalid object type', allowed_objects: VALID_OBJECTS } }
end
end

def handle_tool_destruction
vcs_repository = Travis::RemoteVCS::Repository.new
vcs_repository.destroy(repository_id: @object_id)
end

def handle_space_destruction
vcs_organization = Travis::RemoteVCS::Organization.new
vcs_organization.destroy(org_id: @object_id)
end
end
end
end
72 changes: 72 additions & 0 deletions spec/lib/services/assembla_notify_service_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'spec_helper'
require 'travis/services/assembla_notify_service'

RSpec.describe Travis::Services::AssemblaNotifyService do
let(:payload) { { action: 'destroy', object: 'tool', id: '12345' } }
let(:service) { described_class.new(payload) }
let(:vcs_repository) { instance_double(Travis::RemoteVCS::Repository) }
let(:vcs_organization) { instance_double(Travis::RemoteVCS::Organization) }

before do
allow(Travis::RemoteVCS::Repository).to receive(:new).and_return(vcs_repository)
allow(vcs_repository).to receive(:destroy)
allow(Travis::RemoteVCS::Organization).to receive(:new).and_return(vcs_organization)
allow(vcs_organization).to receive(:destroy)
allow(Travis.logger).to receive(:error)
end

describe '#run' do
context 'with a valid payload for tool destruction' do
it 'calls handle_tool_destruction' do
expect(service).to receive(:handle_tool_destruction)
service.run
end
end

context 'with a valid payload for space destruction' do
let(:payload) { { action: 'destroy', object: 'space', id: '67890' } }

it 'calls handle_space_destruction' do
expect(service).to receive(:handle_space_destruction)
service.run
end
end

context 'with an invalid object type' do
let(:payload) { { action: 'destroy', object: 'repository', id: '12345' } }

it 'returns an error' do
result = service.run
expect(result[:status]).to eq(400)
end
end

context 'with an unsupported object type for destruction' do
before do
stub_const("Travis::Services::AssemblaNotifyService::VALID_OBJECTS", %w[space tool unsupported])
end
let(:payload) { { action: 'destroy', object: 'unsupported', id: '12345' } }

it 'returns an error' do
result = service.run
expect(result[:status]).to eq(400)
end
end
end

describe '#handle_tool_destruction' do
it 'destroys the repository using RemoteVCS' do
expect(vcs_repository).to receive(:destroy).with(repository_id: '12345')
service.send(:handle_tool_destruction)
end
end

describe '#handle_space_destruction' do
let(:payload) { { action: 'destroy', object: 'space', id: '67890' } }

it 'destroys the organization using RemoteVCS' do
expect(vcs_organization).to receive(:destroy).with(org_id: '67890')
service.send(:handle_space_destruction)
end
end
end
27 changes: 27 additions & 0 deletions spec/travis/remote_vcs/organization_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'spec_helper'
require 'travis/remote_vcs/organization'

RSpec.describe Travis::RemoteVCS::Organization do
let(:client) { described_class.new }
let(:org_id) { '12345' }
let(:subject) { client.destroy(org_id: org_id) }

describe '#destroy' do
it 'sends a delete request to the correct URL' do
request = double('request')
expect(request).to receive(:url).with("organizations/#{org_id}")
expect(client).to receive(:request).with(:delete, :destroy, false).and_yield(request)
subject
end

context 'when request is successful' do
before { allow(client).to receive(:request).and_return(true) }
it { is_expected.to be true }
end

context 'when the request fails' do
before { allow(client).to receive(:request).and_return(false) }
it { is_expected.to be false }
end
end
end
27 changes: 27 additions & 0 deletions spec/travis/remote_vcs/repository_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,31 @@
expect(request).to have_been_made
end
end

describe '#destroy' do
subject { repository.destroy(repository_id: id) }

context 'when the request is successful' do
let!(:request) do
stub_request(:delete, /repos\/#{id}/)
.to_return(status: 204)
end

it 'performs a proper request' do
subject
expect(request).to have_been_made
end
end

context 'when the request fails' do
let!(:request) do
stub_request(:delete, /repos\/#{id}/)
.to_return(status: 500)
end

it 'returns false' do
expect(subject).to be false
end
end
end
end