Skip to content

Commit ab5b727

Browse files
[TBT-381] Feedback incorporated
1 parent 7b5948a commit ab5b727

File tree

6 files changed

+63
-112
lines changed

6 files changed

+63
-112
lines changed

lib/travis/api/app/endpoint/assembla.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ class Endpoint
1212
class Assembla < Endpoint
1313
include Travis::Api::App::JWTUtils
1414

15-
REQUIRED_JWT_FIELDS = %w[name email login space_id id access_token refresh_token].freeze
15+
REQUIRED_JWT_FIELDS = %w[name email login space_id id refresh_token].freeze
1616
CLUSTER_HEADER = 'HTTP_X_ASSEMBLA_CLUSTER'.freeze
1717

18-
1918
set prefix: '/assembla'
2019
set :check_auth, false
2120

@@ -30,13 +29,11 @@ class Assembla < Endpoint
3029
org = service.find_or_create_organization(user)
3130
service.create_org_subscription(user, org.id)
3231

33-
34-
response = {
32+
{
3533
user_id: user.id,
3634
login: user.login,
37-
token: user.token,
38-
status: 'signed_in'
39-
}.to_json
35+
token: user.token
36+
}
4037
end
4138

4239
private
@@ -51,7 +48,7 @@ def validate_request!
5148
def check_required_fields
5249
missing = REQUIRED_JWT_FIELDS.select { |f| @jwt_payload[f].nil? || @jwt_payload[f].to_s.strip.empty? }
5350
unless missing.empty?
54-
halt 400, { error: 'Missing required fields', missing: missing }.to_json
51+
halt 400, { error: 'Missing required fields', missing: missing }
5552
end
5653
end
5754

@@ -62,7 +59,7 @@ def deep_integration_enabled?
6259
def valid_asm_cluster?
6360
allowed = Array(Travis.config.assembla_clusters.to_s.split(','))
6461
cluster = request.env[CLUSTER_HEADER]
65-
!cluster.nil? && allowed.include?(cluster)
62+
allowed.include?(cluster)
6663
end
6764
end
6865
end

lib/travis/api/app/jwt_utils.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
class Travis::Api::App
22
module JWTUtils
33
def extract_jwt_token(request)
4-
request.env['HTTP_AUTHORIZATION']&.split(' ')&.last
4+
request.env['HTTP_AUTHORIZATION']&.split&.last
55
end
66

77
def verify_jwt(request)
8-
secret = Travis.config.assembla_jwt_secret
98
token = extract_jwt_token(request)
109

11-
halt 401, { error: "Missing JWT" }.to_json unless token
10+
halt 401, { error: "Missing JWT" } unless token
1211

1312
begin
14-
decoded, = JWT.decode(token, secret, true, { algorithm: 'HS256' })
13+
decoded, = JWT.decode(token, Travis.config.assembla_jwt_secret, true, algorithm: 'HS256' )
1514
decoded
1615
rescue JWT::DecodeError => e
17-
halt 401, { error: "Invalid JWT: #{e.message}" }.to_json
16+
halt 401, { error: "Invalid JWT: #{e.message}" }
1817
end
1918
end
2019
end

lib/travis/config/defaults.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ def fallback_logs_api_auth_token
107107
antifraud: { captcha_max_failed_attempts: 3, captcha_block_duration: 24, credit_card_max_failed_attempts: 3, credit_card_block_duration: 24 },
108108
legacy_roles: false,
109109
internal_users: [{id: 0, login: 'cron'}],
110-
deep_integration_enabled: ENV['DEEP_INTEGRATION_ENABLED'],
111-
assembla_clusters: ENV['ASSEMBLA_CLUSTERS'],
112-
assembla_jwt_secret: ENV['ASSEMBLA_JWT_SECRET']
110+
assembla_clusters: 'eu, us',
111+
deep_integration_enabled: false,
112+
assembla_jwt_secret: 'assembla_jwt_secret',
113+
beta_plan_name: 'beta_plan'
113114

114115
default :_access => [:key]
115116

lib/travis/services/assembla_user_service.rb

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,39 @@ module Services
33
class AssemblaUserService
44
class SyncError < StandardError; end
55

6+
BILLING_COUNTRY = 'Poland'
7+
BILLING_ADDRESS = "System-generated for user %{login} (%{id})"
8+
BILLING_CITY = "AutoCity-%{id}"
9+
BILLING_ZIP = "000%{id}"
10+
611
def initialize(payload)
712
@payload = payload
813
end
914

1015
def find_or_create_user
11-
attrs = {
16+
user = ::User.find_or_initialize_by(
17+
name: @payload['name'],
1218
vcs_id: @payload['id'],
1319
email: @payload['email'],
1420
login: @payload['login'],
1521
vcs_type: 'AssemblaUser'
16-
}
17-
18-
user = ::User.find_or_create_by!(attrs)
19-
user.update(vcs_oauth_token: @payload['refresh_token'])
22+
)
23+
user.vcs_oauth_token = @payload['refresh_token']
24+
user.save!
2025
sync_user(user.id)
2126
user
2227
end
2328

2429
def find_or_create_organization(user)
25-
attrs = {
30+
user.organizations.find_or_create_by!(
2631
vcs_id: @payload['space_id'],
2732
vcs_type: 'AssemblaOrganization'
28-
}
29-
user.organizations.find_or_create_by(attrs)
33+
)
3034
end
3135

3236
def create_org_subscription(user, organization_id)
33-
client = Travis::API::V3::BillingClient.new(user.id)
34-
client.create_v2_subscription(subscription_params(user, organization_id))
37+
billing_client = Travis::API::V3::BillingClient.new(user.id)
38+
billing_client.create_v2_subscription(subscription_params(user, organization_id))
3539
rescue => e
3640
{ error: true, details: e.message }
3741
end
@@ -46,7 +50,7 @@ def sync_user(user_id)
4650

4751
def subscription_params(user, organization_id)
4852
{
49-
'plan' => 'beta_plan',
53+
'plan' => Travis.config.beta_plan_name,
5054
'organization_id' => organization_id,
5155
'billing_info' => billing_info(user),
5256
'credit_card_info' => { 'token' => nil }
@@ -55,12 +59,12 @@ def subscription_params(user, organization_id)
5559

5660
def billing_info(user)
5761
{
58-
'address' => "System-generated for user #{user.login} (#{user.id})",
59-
'city' => "AutoCity-#{user.id}",
60-
'country' => 'Poland',
62+
'address' => BILLING_ADDRESS % { login: user.login, id: user.id },
63+
'city' => BILLING_CITY % { id: user.id },
64+
'country' => BILLING_COUNTRY,
6165
'first_name' => user.name&.split&.first,
6266
'last_name' => user.name&.split&.last,
63-
'zip_code' => "000#{user.id}",
67+
'zip_code' => BILLING_ZIP % { id: user.id },
6468
'billing_email' => user.email
6569
}
6670
end
Lines changed: 22 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
require 'spec_helper'
2+
require 'factory_bot'
23

34
RSpec.describe Travis::Services::AssemblaUserService do
45
let(:payload) do
56
{
67
'id' => '12345',
8+
'name' => 'Test User',
79
'email' => '[email protected]',
810
'login' => 'testuser',
911
'refresh_token' => 'refresh123',
@@ -12,39 +14,30 @@
1214
end
1315

1416
let(:service) { described_class.new(payload) }
15-
let(:user) { double('User', id: 1, login: 'testuser', email: '[email protected]', name: 'Test User') }
16-
let(:organization) { double('Organization', id: 2) }
17-
18-
describe '#initialize' do
19-
it 'stores the payload' do
20-
expect(service.instance_variable_get(:@payload)).to eq(payload)
21-
end
22-
end
17+
let(:user) { FactoryBot.create(:user, vcs_id: payload['id'], email: payload['email'], login: payload['login'], name: payload['name']) }
18+
let(:organization) { FactoryBot.create(:org, vcs_id: payload['space_id'], vcs_type: 'AssemblaOrganization') }
2319

2420
describe '#find_or_create_user' do
2521
let(:expected_attrs) do
2622
{
27-
vcs_id: '12345',
28-
29-
login: 'testuser',
23+
vcs_id: payload['id'],
24+
email: payload['email'],
25+
name: payload['name'],
26+
login: payload['login'],
3027
vcs_type: 'AssemblaUser'
3128
}
3229
end
3330

3431
before do
35-
allow(::User).to receive(:find_or_create_by!).with(expected_attrs).and_return(user)
36-
allow(user).to receive(:update)
3732
allow(Travis::RemoteVCS::User).to receive(:new).and_return(double(sync: true))
3833
end
3934

4035
it 'finds or creates a user with correct attributes' do
41-
expect(::User).to receive(:find_or_create_by!).with(expected_attrs)
42-
service.find_or_create_user
43-
end
44-
45-
it 'returns the user' do
46-
result = service.find_or_create_user
47-
expect(result).to eq(user)
36+
service_user = service.find_or_create_user
37+
expect(service_user.login).to eq(expected_attrs[:login])
38+
expect(service_user.email).to eq(expected_attrs[:email])
39+
expect(service_user.name).to eq(expected_attrs[:name])
40+
expect(service_user.vcs_id).to eq(expected_attrs[:vcs_id])
4841
end
4942

5043
context 'when sync fails' do
@@ -60,60 +53,26 @@
6053
end
6154

6255
describe '#find_or_create_organization' do
63-
let(:organizations_relation) { double('organizations') }
6456
let(:expected_attrs) do
6557
{
66-
vcs_id: '67890',
58+
vcs_id: payload['space_id'],
6759
vcs_type: 'AssemblaOrganization'
6860
}
6961
end
7062

71-
before do
72-
allow(user).to receive(:organizations).and_return(organizations_relation)
73-
end
74-
7563
it 'finds or creates organization with correct attributes' do
76-
expect(organizations_relation).to receive(:find_or_create_by).with(expected_attrs).and_return(organization)
64+
service_org = service.find_or_create_organization(user)
7765

78-
result = service.find_or_create_organization(user)
79-
expect(result).to eq(organization)
66+
expect(service_org.vcs_type).to eq(expected_attrs[:vcs_type])
67+
expect(service_org.vcs_id).to eq(expected_attrs[:vcs_id])
8068
end
8169
end
8270

8371
describe '#create_org_subscription' do
8472
let(:billing_client) { double('BillingClient') }
85-
let(:expected_subscription_params) do
86-
{
87-
'plan' => 'beta_plan',
88-
'organization_id' => 2,
89-
'billing_info' => {
90-
'address' => 'System-generated for user testuser (1)',
91-
'city' => 'AutoCity-1',
92-
'country' => 'Poland',
93-
'first_name' => 'Test',
94-
'last_name' => 'User',
95-
'zip_code' => '0001',
96-
'billing_email' => '[email protected]'
97-
},
98-
'credit_card_info' => { 'token' => nil }
99-
}
100-
end
10173

10274
before do
103-
allow(Travis::API::V3::BillingClient).to receive(:new).with(1).and_return(billing_client)
104-
end
105-
106-
it 'creates a billing client with user id' do
107-
expect(Travis::API::V3::BillingClient).to receive(:new).with(1)
108-
allow(billing_client).to receive(:create_v2_subscription)
109-
110-
service.create_org_subscription(user, 2)
111-
end
112-
113-
it 'calls create_v2_subscription with correct params' do
114-
expect(billing_client).to receive(:create_v2_subscription).with(expected_subscription_params)
115-
116-
service.create_org_subscription(user, 2)
75+
allow(Travis::API::V3::BillingClient).to receive(:new).with(user.id).and_return(billing_client)
11776
end
11877

11978
context 'when billing client raises an error' do
@@ -122,23 +81,10 @@
12281
it 'returns error hash' do
12382
allow(billing_client).to receive(:create_v2_subscription).and_raise(error)
12483

125-
result = service.create_org_subscription(user, 2)
126-
expect(result).to eq({ error: true, details: 'Billing error' })
127-
end
128-
end
129-
130-
context 'when user has no name' do
131-
let(:user_without_name) { double('User', id: 1, login: 'testuser', email: '[email protected]', name: nil) }
132-
133-
it 'handles nil name gracefully' do
134-
expected_params = expected_subscription_params.dup
135-
expected_params['billing_info']['first_name'] = nil
136-
expected_params['billing_info']['last_name'] = nil
137-
138-
expect(billing_client).to receive(:create_v2_subscription).with(expected_params)
139-
140-
service.create_org_subscription(user_without_name, 2)
84+
result = service.create_org_subscription(user, organization.id)
85+
expect(result[:error]).to be_truthy
86+
expect(result[:details]).to be_present
14187
end
14288
end
14389
end
144-
end
90+
end

spec/unit/endpoint/assembla_spec.rb

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@
2222
let(:organization) { double('Organization', id: 1) }
2323
let(:organizations) { double('Organizations') }
2424
let(:subscription_response) { { 'status' => 'subscribed' } }
25+
let(:assembla_cluster) { 'cluster1' }
2526

2627
before do
2728
Travis.config[:deep_integration_enabled] = true
28-
Travis.config[:assembla_clusters] = 'cluster1'
29+
Travis.config[:assembla_clusters] = assembla_cluster
2930
Travis.config[:assembla_jwt_secret] = jwt_secret
3031

31-
header 'X_ASSEMBLA_CLUSTER', 'cluster1'
32+
header 'X_ASSEMBLA_CLUSTER', assembla_cluster
33+
end
34+
35+
after do
36+
Travis.config[:deep_integration_enabled] = false
3237
end
3338

3439
describe 'POST /assembla/login' do
@@ -50,9 +55,8 @@
5055

5156
expect(last_response.status).to eq(200)
5257
body = JSON.parse(last_response.body)
53-
expect(body['login']).to eq('testuser')
58+
expect(body['login']).to eq(user.login)
5459
expect(body['token']).to eq('abc123')
55-
expect(body['status']).to eq('signed_in')
5660
end
5761
end
5862

0 commit comments

Comments
 (0)