maestrano-connector-rails 0.4.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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