maestrano-connector-rails 0.4.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -0
  3. data/README.md +1 -1
  4. data/VERSION +1 -1
  5. data/app/controllers/maestrano/connec_controller.rb +17 -19
  6. data/app/jobs/maestrano/connector/rails/all_synchronizations_job.rb +1 -1
  7. data/app/jobs/maestrano/connector/rails/push_to_connec_job.rb +11 -11
  8. data/app/jobs/maestrano/connector/rails/synchronization_job.rb +20 -11
  9. data/app/models/maestrano/connector/rails/concerns/complex_entity.rb +66 -74
  10. data/app/models/maestrano/connector/rails/concerns/connec_helper.rb +102 -0
  11. data/app/models/maestrano/connector/rails/concerns/entity.rb +233 -231
  12. data/app/models/maestrano/connector/rails/concerns/external.rb +7 -0
  13. data/app/models/maestrano/connector/rails/concerns/sub_entity_base.rb +14 -43
  14. data/app/models/maestrano/connector/rails/connec_helper.rb +5 -0
  15. data/app/models/maestrano/connector/rails/organization.rb +8 -2
  16. data/db/20160524112054_add_encryption_on_oauth_keys.rb +8 -0
  17. data/lib/generators/connector/install_generator.rb +1 -0
  18. data/lib/generators/connector/templates/complex_entity_example/contact.rb +1 -1
  19. data/lib/generators/connector/templates/complex_entity_example/contact_and_lead.rb +2 -2
  20. data/lib/generators/connector/templates/entity.rb +13 -19
  21. data/lib/generators/connector/templates/example_entity.rb +2 -2
  22. data/lib/generators/connector/templates/example_entity_spec.rb +73 -0
  23. data/lib/generators/connector/templates/external.rb +11 -0
  24. data/maestrano-connector-rails.gemspec +13 -8
  25. data/release_notes.md +81 -0
  26. data/spec/controllers/connec_controller_spec.rb +19 -6
  27. data/spec/dummy/app/models/maestrano/connector/rails/entity.rb +0 -7
  28. data/spec/dummy/app/models/maestrano/connector/rails/external.rb +7 -0
  29. data/spec/dummy/app/views/home/index.html.erb +1 -36
  30. data/spec/factories.rb +3 -0
  31. data/spec/integration/connec_to_external_spec.rb +188 -0
  32. data/spec/integration/external_to_connec_spec.rb +155 -0
  33. data/spec/integration/integration_complex_spec.rb +281 -0
  34. data/spec/integration/singleton_spec.rb +288 -0
  35. data/spec/jobs/all_synchronizations_job_spec.rb +5 -0
  36. data/spec/jobs/push_to_connec_job_spec.rb +3 -6
  37. data/spec/jobs/synchronization_job_spec.rb +29 -17
  38. data/spec/models/complex_entity_spec.rb +257 -412
  39. data/spec/models/connec_helper_spec.rb +143 -0
  40. data/spec/models/entity_spec.rb +420 -348
  41. data/spec/models/external_spec.rb +4 -0
  42. data/spec/models/organization_spec.rb +2 -1
  43. data/spec/models/sub_entity_base_spec.rb +28 -69
  44. data/template/factories.rb +3 -1
  45. data/template/maestrano-connector-template.rb +11 -13
  46. data/template/maestrano.rb +2 -1
  47. data/template/settings/development.yml +4 -2
  48. data/template/settings/production.yml +1 -11
  49. data/template/settings/settings.yml +8 -0
  50. data/template/settings/test.yml +2 -0
  51. data/template/settings/uat.yml +1 -9
  52. metadata +12 -7
  53. data/Gemfile.lock +0 -256
  54. data/realse_notes.md +0 -16
  55. data/spec/dummy/app/views/admin/index.html.erb +0 -51
  56. data/spec/dummy/db/development.sqlite3 +0 -0
  57. data/spec/dummy/db/test.sqlite3 +0 -0
@@ -31,10 +31,19 @@ describe Maestrano::ConnecController, type: :controller do
31
31
  expect(response.status).to eq(200)
32
32
  end
33
33
 
34
+ context 'with an unexpected error' do
35
+ let(:notifications) { {'people' => [entity]} }
36
+ it 'logs a warning' do
37
+ allow(controller).to receive(:find_entity_class).and_raise('Bruno c\'est peter')
38
+ expect(Rails.logger).to receive(:warn)
39
+ subject
40
+ end
41
+ end
42
+
34
43
  context "with an unknown entity" do
35
44
  let(:notifications) { {'people' => [entity]} }
36
45
  before {
37
- allow(Maestrano::Connector::Rails::Entity).to receive(:entities_list).and_return(%w())
46
+ allow(Maestrano::Connector::Rails::External).to receive(:entities_list).and_return(%w())
38
47
  }
39
48
 
40
49
  it 'logs a warning' do
@@ -47,7 +56,7 @@ describe Maestrano::ConnecController, type: :controller do
47
56
  let(:notifications) { {'leads' => [entity]} }
48
57
 
49
58
  before {
50
- allow(Maestrano::Connector::Rails::Entity).to receive(:entities_list).and_return(%w(contact_and_lead))
59
+ allow(Maestrano::Connector::Rails::External).to receive(:entities_list).and_return(%w(contact_and_lead))
51
60
  class Entities::ContactAndLead < Maestrano::Connector::Rails::ComplexEntity
52
61
  def self.connec_entities_names
53
62
  %w(Lead)
@@ -71,11 +80,15 @@ describe Maestrano::ConnecController, type: :controller do
71
80
 
72
81
  it 'process the data and push them' do
73
82
  expect_any_instance_of(Entities::ContactAndLead).to receive(:before_sync)
74
- expect_any_instance_of(Entities::ContactAndLead).to receive(:filter_connec_entities).and_return({"Lead"=>[entity]})
75
- expect_any_instance_of(Entities::ContactAndLead).to receive(:consolidate_and_map_data).with({"Lead"=>[entity]}, {}, organization, {}).and_return({})
83
+ expect_any_instance_of(Entities::ContactAndLead).to receive(:filter_connec_entities).with({"Lead"=>[entity]}).and_return({"Lead"=>[entity]})
84
+ expect_any_instance_of(Entities::ContactAndLead).to receive(:consolidate_and_map_data).with({"Lead"=>[entity]}, {}).and_return({})
76
85
  expect_any_instance_of(Entities::ContactAndLead).to receive(:push_entities_to_external)
77
86
  expect_any_instance_of(Entities::ContactAndLead).to receive(:after_sync)
87
+ begin
78
88
  subject
89
+ rescue => e
90
+ puts e
91
+ end
79
92
  end
80
93
  end
81
94
  end
@@ -84,7 +97,7 @@ describe Maestrano::ConnecController, type: :controller do
84
97
  let(:notifications) { {'people' => [entity]} }
85
98
 
86
99
  before {
87
- allow(Maestrano::Connector::Rails::Entity).to receive(:entities_list).and_return(%w(person))
100
+ allow(Maestrano::Connector::Rails::External).to receive(:entities_list).and_return(%w(person))
88
101
  class Entities::Person < Maestrano::Connector::Rails::Entity
89
102
  def self.connec_entity_name
90
103
  'People'
@@ -140,7 +153,7 @@ describe Maestrano::ConnecController, type: :controller do
140
153
  it 'process the data and push them' do
141
154
  expect_any_instance_of(Entities::Person).to receive(:before_sync)
142
155
  expect_any_instance_of(Entities::Person).to receive(:filter_connec_entities).and_return([entity])
143
- expect_any_instance_of(Entities::Person).to receive(:consolidate_and_map_data).with([entity], [], organization, {}).and_return({})
156
+ expect_any_instance_of(Entities::Person).to receive(:consolidate_and_map_data).with([entity], []).and_return({})
144
157
  expect_any_instance_of(Entities::Person).to receive(:push_entities_to_external)
145
158
  expect_any_instance_of(Entities::Person).to receive(:after_sync)
146
159
  subject
@@ -1,10 +1,3 @@
1
1
  class Maestrano::Connector::Rails::Entity
2
2
  include Maestrano::Connector::Rails::Concerns::Entity
3
-
4
- # Return an array of all the entities that the connector can synchronize
5
- # If you add new entities, you need to generate
6
- # a migration to add them to existing organizations
7
- def self.entities_list
8
- %w(entity1 entity2)
9
- end
10
3
  end
@@ -8,4 +8,11 @@ class Maestrano::Connector::Rails::External
8
8
  def self.get_client(organization)
9
9
  nil
10
10
  end
11
+
12
+ # Return an array of all the entities that the connector can synchronize
13
+ # If you add new entities, you need to generate
14
+ # a migration to add them to existing organizations
15
+ def self.entities_list
16
+ %w(entity1 entity2)
17
+ end
11
18
  end
@@ -1,36 +1 @@
1
- <!-- TODO -->
2
- <!-- This template is given as an example of the informations that
3
- can be displayed on the home page -->
4
- <!-- This view is designed according to the home controller supplied -->
5
- <div class='home'>
6
- <%= link_to 'Admin panel', admin_index_path %>
7
- <% unless current_user %>
8
- <%= link_to "Link your Maestrano account", maestrano_connector_rails.init_maestrano_auth_saml_index_path(tenant: :default) %>
9
- <p>Some generic information about the connector</p>
10
-
11
- <% else %>
12
- <% unless @organization.oauth_uid %>
13
- Not linked to external app
14
- <% if is_admin?(current_user, @organization) %>
15
- <%= link_to "Link this company to ...", 'some_path' %>
16
- <% end %>
17
- <% else %>
18
- Linked to external app
19
- <% end %>
20
-
21
- <h2>Last synchronization</h2>
22
- <% if @synchronizations.first %>
23
- <%= "#{@synchronizations.first.updated_at} #{@synchronizations.first.status}" %>
24
- <%= @synchronizations.first.message if @synchronizations.first.status == 'ERROR' %>
25
- <% end %>
26
-
27
- <h2>Synchronizations history</h2>
28
- <% @synchronizations.each do |sync| %>
29
- <%= sync.updated_at %>
30
- <%= sync.status %>
31
- <% if sync.status == 'ERROR' %>
32
- <%= sync.message %>
33
- <% end %>
34
- <% end %>
35
- <% end %>
36
- </div>
1
+ <!-- TODO -->
data/spec/factories.rb CHANGED
@@ -8,6 +8,9 @@ FactoryGirl.define do
8
8
  factory :organization, class: Maestrano::Connector::Rails::Organization do
9
9
  name "My company"
10
10
  tenant "default"
11
+ sequence(:uid) { |n| "cld-11#{n}" }
12
+ oauth_uid 'sfuiy765'
13
+ oauth_provider 'this_app'
11
14
  end
12
15
 
13
16
  factory :user_organization_rel, class: Maestrano::Connector::Rails::UserOrganizationRel do
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'connec to the external application' do
4
+
5
+ class Entities::ConnecToExternal < Maestrano::Connector::Rails::Entity
6
+ def self.external_entity_name
7
+ 'Contact'
8
+ end
9
+
10
+ def self.connec_entity_name
11
+ 'Person'
12
+ end
13
+
14
+ def self.mapper_class
15
+ PersonMapper
16
+ end
17
+
18
+ def self.references
19
+ ['organization_id']
20
+ end
21
+
22
+ def self.object_name_from_connec_entity_hash(entity)
23
+ entity['first_name']
24
+ end
25
+
26
+ class PersonMapper
27
+ extend HashMapper
28
+ map from('organization_id'), to('AccountId')
29
+ map from('first_name'), to('FirstName')
30
+ end
31
+ end
32
+
33
+ let(:provider) { 'provider' }
34
+ let(:oauth_uid) { 'oauth uid' }
35
+ let!(:organization) { create(:organization, oauth_provider: provider, oauth_uid: oauth_uid) }
36
+ let(:connec_client) { Maestrano::Connec::Client[organization.tenant].new(organization.uid) }
37
+ let(:external_client) { Object.new }
38
+ let(:ext_org_id) { 'ext org id' }
39
+ let(:ext_contact_id) { 'ext contact id' }
40
+ let(:person1) {
41
+ {
42
+ "id" => [
43
+ {
44
+ "realm" => "org-fg4a",
45
+ "provider" => "connec",
46
+ "id" => "23daf041-e18e-0133-7b6a-15461b913fab"
47
+ }
48
+ ],
49
+ "code" => "PE3",
50
+ "status" => "ACTIVE",
51
+ "first_name" => "John",
52
+ "last_name" => "Doe",
53
+ "organization_id" => [
54
+ {
55
+ "realm" => "org-fg4a",
56
+ "provider" => "connec",
57
+ "id" => "2305c5e0-e18e-0133-890f-07d4de9f9781"
58
+ },
59
+ {
60
+ "realm" => oauth_uid,
61
+ "provider" => provider,
62
+ "id" => ext_org_id
63
+ }
64
+ ],
65
+ "is_customer" => false,
66
+ "is_supplier" => true,
67
+ "is_lead" => false
68
+ }
69
+ }
70
+ let(:person) { person1 }
71
+
72
+
73
+ before {
74
+ allow(connec_client).to receive(:get).and_return(ActionDispatch::Response.new(200, {}, {people: [person]}.to_json, {}))
75
+ allow(connec_client).to receive(:batch).and_return(ActionDispatch::Response.new(200, {}, {results: [{status: 200, body: {people: {}}}]}.to_json, {}))
76
+
77
+ allow_any_instance_of(Entities::ConnecToExternal).to receive(:get_external_entities).and_return([])
78
+ }
79
+
80
+ subject { Maestrano::Connector::Rails::SynchronizationJob.new.sync_entity('connec_to_external', organization, connec_client, external_client, nil, {}) }
81
+
82
+ describe 'a new record created in connec with all references known' do
83
+ before {
84
+ allow_any_instance_of(Entities::ConnecToExternal).to receive(:create_external_entity).and_return(ext_contact_id)
85
+ }
86
+
87
+ let(:mapped_entity) {
88
+ {
89
+ AccountId: ext_org_id,
90
+ FirstName: 'John'
91
+ }
92
+ }
93
+
94
+ let(:batch_params) {
95
+ {
96
+ :sequential=>true,
97
+ :ops=> [
98
+ {
99
+ :method=>"put",
100
+ :url=>"/api/v2/#{organization.uid}/people/23daf041-e18e-0133-7b6a-15461b913fab",
101
+ :params=>
102
+ {
103
+ :people=>{
104
+ id: [
105
+ {
106
+ :id=>"ext contact id",
107
+ :provider=>"provider",
108
+ :realm=>"oauth uid"
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ }
114
+ ]
115
+ }
116
+ }
117
+
118
+ it 'handles the idmap correctly' do
119
+ expect{
120
+ subject
121
+ }.to change{ Maestrano::Connector::Rails::IdMap.count }.by(1)
122
+ idmap = Maestrano::Connector::Rails::IdMap.last
123
+ expect(idmap.name).to eql('John')
124
+ expect(idmap.connec_entity).to eql('person')
125
+ expect(idmap.external_entity).to eql('contact')
126
+ expect(idmap.message).to be_nil
127
+ expect(idmap.external_id).to eql(ext_contact_id)
128
+ expect(idmap.connec_id).to eql("23daf041-e18e-0133-7b6a-15461b913fab")
129
+ end
130
+
131
+ it 'does the mapping correctly' do
132
+ idmap = Entities::ConnecToExternal.create_idmap(organization_id: organization.id, external_id: ext_contact_id, connec_id: "23daf041-e18e-0133-7b6a-15461b913fab")
133
+ allow(Entities::ConnecToExternal).to receive(:create_idmap).and_return(idmap)
134
+ expect_any_instance_of(Entities::ConnecToExternal).to receive(:push_entities_to_external).with([{entity: mapped_entity, idmap: idmap}])
135
+ subject
136
+ end
137
+
138
+ it 'send the external id to connec' do
139
+ expect(connec_client).to receive(:batch).with(batch_params)
140
+ subject
141
+ end
142
+ end
143
+
144
+ describe 'an update from connec with all references known' do
145
+ before {
146
+ allow_any_instance_of(Entities::ConnecToExternal).to receive(:update_external_entity).and_return(nil)
147
+ }
148
+ let(:person) { person1.merge('first_name' => 'Jane', 'id' => person1['id'] << {'id' => ext_contact_id, 'provider' => provider, 'realm' => oauth_uid}) }
149
+ let!(:idmap) { Entities::ConnecToExternal.create_idmap(organization_id: organization.id, external_id: ext_contact_id, connec_id: "23daf041-e18e-0133-7b6a-15461b913fab") }
150
+
151
+ let(:mapped_entity) {
152
+ {
153
+ AccountId: ext_org_id,
154
+ FirstName: 'Jane'
155
+ }
156
+ }
157
+
158
+ it 'update the idmap' do
159
+ subject
160
+ expect(idmap.reload.message).to be_nil
161
+ expect(idmap.reload.name).to eql('Jane')
162
+ expect(idmap.reload.last_push_to_external > 1.minute.ago).to be true
163
+ end
164
+
165
+ it 'does the mapping correctly' do
166
+ expect_any_instance_of(Entities::ConnecToExternal).to receive(:push_entities_to_external).with([{entity: mapped_entity, idmap: idmap}])
167
+ subject
168
+ end
169
+
170
+ it 'does not send the external id to connec' do
171
+ expect(connec_client).to_not receive(:batch)
172
+ subject
173
+ end
174
+
175
+ end
176
+
177
+ describe 'a creation from connec with references missing' do
178
+ let(:person) { person1.merge("organization_id" => [{"realm"=>"org-fg4a", "provider"=>"connec", "id"=>"2305c5e0-e18e-0133-890f-07d4de9f9781"}]) }
179
+
180
+ it 'pushes nothing and creates no idmap' do
181
+ expect_any_instance_of(Entities::ConnecToExternal).to_not receive(:create_external_entity)
182
+ expect_any_instance_of(Entities::ConnecToExternal).to_not receive(:update_external_entity)
183
+ expect{
184
+ subject
185
+ }.to_not change{ Maestrano::Connector::Rails::IdMap.count }
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'external application to connec' do
4
+
5
+ class Entities::ExternalToConnec < Maestrano::Connector::Rails::Entity
6
+ def self.external_entity_name
7
+ 'Contact'
8
+ end
9
+
10
+ def self.connec_entity_name
11
+ 'Person'
12
+ end
13
+
14
+ def self.mapper_class
15
+ PersonMapper
16
+ end
17
+
18
+ def self.references
19
+ ['organization_id']
20
+ end
21
+
22
+ def self.object_name_from_external_entity_hash(entity)
23
+ entity['FirstName']
24
+ end
25
+
26
+ def self.id_from_external_entity_hash(entity)
27
+ entity['Id']
28
+ end
29
+
30
+ class PersonMapper
31
+ extend HashMapper
32
+ map from('organization_id'), to('AccountId')
33
+ map from('first_name'), to('FirstName')
34
+ end
35
+ end
36
+
37
+ let(:provider) { 'provider' }
38
+ let(:oauth_uid) { 'oauth uid' }
39
+ let!(:organization) { create(:organization, oauth_provider: provider, oauth_uid: oauth_uid) }
40
+ let(:connec_client) { Maestrano::Connec::Client[organization.tenant].new(organization.uid) }
41
+ let(:external_client) { Object.new }
42
+
43
+ let(:ext_org_id) { 'ext org id' }
44
+ let(:ext_contact_id) { 'ext contact id' }
45
+
46
+ let(:contact1) {
47
+ {
48
+ 'Id' => ext_contact_id,
49
+ 'FirstName' => 'Jack',
50
+ 'AccountId' => ext_org_id
51
+ }
52
+ }
53
+ let(:contact) { contact1 }
54
+
55
+ let(:mapped_entity1) {
56
+ {
57
+ id: [
58
+ {
59
+ provider: provider,
60
+ realm: oauth_uid,
61
+ id: ext_contact_id
62
+ }
63
+ ],
64
+ first_name: 'Jack',
65
+ organization_id: [
66
+ {
67
+ provider: provider,
68
+ realm: oauth_uid,
69
+ id: ext_org_id
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ let(:mapped_entity) { mapped_entity1 }
75
+
76
+ let(:batch_call) {
77
+ {
78
+ :sequential => true,
79
+ :ops => [
80
+ {
81
+ :method => "post",
82
+ :url => "/api/v2/#{organization.uid}/people/",
83
+ :params => {
84
+ :people => mapped_entity
85
+ }
86
+ }
87
+ ]
88
+ }
89
+ }
90
+
91
+ before {
92
+ allow_any_instance_of(Entities::ExternalToConnec).to receive(:get_connec_entities).and_return([])
93
+ allow_any_instance_of(Entities::ExternalToConnec).to receive(:get_external_entities).and_return([contact])
94
+ }
95
+
96
+ subject { Maestrano::Connector::Rails::SynchronizationJob.new.sync_entity('external_to_connec', organization, connec_client, external_client, nil, {}) }
97
+
98
+ describe 'creating an record in connec' do
99
+ before {
100
+ allow(connec_client).to receive(:batch).and_return(ActionDispatch::Response.new(201, {}, {results: [{status: 201, body: {people: {id: [{provider: 'connec', id: 'connec-id'}]}}}]}.to_json, {}))
101
+ }
102
+
103
+ it 'handles the idmap correctly' do
104
+ expect{
105
+ subject
106
+ }.to change{ Maestrano::Connector::Rails::IdMap.count }.by(1)
107
+ idmap = Maestrano::Connector::Rails::IdMap.last
108
+ expect(idmap.name).to eql('Jack')
109
+ expect(idmap.connec_entity).to eql('person')
110
+ expect(idmap.external_entity).to eql('contact')
111
+ expect(idmap.message).to be_nil
112
+ expect(idmap.external_id).to eql(ext_contact_id)
113
+ end
114
+
115
+ it 'does the mapping correctly' do
116
+ idmap = Entities::ExternalToConnec.create_idmap(organization_id: organization.id, external_id: ext_contact_id)
117
+ allow(Entities::ExternalToConnec).to receive(:create_idmap).and_return(idmap)
118
+ expect_any_instance_of(Entities::ExternalToConnec).to receive(:push_entities_to_connec).with([{entity: mapped_entity, idmap: idmap}])
119
+ subject
120
+ end
121
+
122
+ it 'does the right call to connec' do
123
+ expect(connec_client).to receive(:batch).with(batch_call)
124
+ subject
125
+ expect(Maestrano::Connector::Rails::IdMap.last.connec_id).to eql('connec-id')
126
+ end
127
+ end
128
+
129
+ describe 'updating an record in connec' do
130
+ before {
131
+ allow(connec_client).to receive(:batch).and_return(ActionDispatch::Response.new(201, {}, {results: [{status: 201, body: {people: {id: [{provider: 'connec', id: 'connec-id'}]}}}]}.to_json, {}))
132
+ }
133
+ let(:contact) { contact1.merge('FirstName' => 'Jacky') }
134
+ let(:mapped_entity) { mapped_entity1.merge(first_name: 'Jacky') }
135
+ let!(:idmap) { Entities::ExternalToConnec.create_idmap(organization_id: organization.id, external_id: ext_contact_id, connec_id: 'connec-id') }
136
+
137
+ it 'handles the idmap correctly' do
138
+ expect{
139
+ subject
140
+ }.to_not change{ Maestrano::Connector::Rails::IdMap.count }
141
+ expect(idmap.reload.name).to eql('Jacky')
142
+ expect(idmap.reload.message).to be_nil
143
+ end
144
+
145
+ it 'does the mapping correctly' do
146
+ expect_any_instance_of(Entities::ExternalToConnec).to receive(:push_entities_to_connec).with([{entity: mapped_entity, idmap: idmap}])
147
+ subject
148
+ end
149
+
150
+ it 'does the right call to connec' do
151
+ expect(connec_client).to receive(:batch).with(batch_call)
152
+ subject
153
+ end
154
+ end
155
+ end