maestrano-connector-rails 0.4.1 → 0.4.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: efabe776dea52e8b1601a0de62be567ad16961a1
4
- data.tar.gz: b2c779a4717875396a79a9dae1bd2d80017d0095
3
+ metadata.gz: 43df5981576854c5a7433146a874a38b6826a030
4
+ data.tar.gz: 865f0399ab96db0323b281f18ab80f7f38359e5d
5
5
  SHA512:
6
- metadata.gz: c064cc33f54e2a4571f93ba294b26ae52a889a678d7b5f7a26968ef3a25997d29108c61a6d0518c19142520e10642cf8b9f8279da08a77a3cb04a1c4b4b8bfde
7
- data.tar.gz: c0ad8eee085a0bf878786773bc61a7e698368c8cf57d0aa0651f8e51c1ed78f1aa9c382f45cd2c6c8c94111aefd780a9643608ea0f0ad8357c94170d51bdc534
6
+ metadata.gz: b9e01ee3b43e36ad310f5babf1819b9a215be1d325afbb378092213cdad72802cac6ae5cf77b4546189b4649a5b4635bf3af1a37c21ba19fa04552c9940b2eac
7
+ data.tar.gz: 77fbe67ebc5496d2f1b8e74ea9237b53fa3b880121fbc73b0a9b4265235c83833e79bdb418c77f4eb0ffdd3a0545af3decdd8cb470f2ad4ae708128cd580a901
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 0.4.2
@@ -25,7 +25,7 @@ class Maestrano::Auth::SamlController < Maestrano::Rails::SamlBaseController
25
25
  end
26
26
 
27
27
  if session[:settings]
28
- session.delete(:setting)
28
+ session.delete(:settings)
29
29
  redirect_to main_app.root_path
30
30
  else
31
31
  if current_organization && current_organization.oauth_uid && current_organization.sync_enabled
@@ -5,31 +5,33 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
5
5
 
6
6
  begin
7
7
  params.except(:tenant, :controller, :action).each do |entity_name, entities|
8
- if entity_instance_hash = find_entity_instance(entity_name)
9
- entity_instance = entity_instance_hash[:instance]
10
-
11
- entities.each do |entity|
12
- if (organization = Maestrano::Connector::Rails::Organization.find_by(uid: entity[:group_id], tenant: params[:tenant])) && organization.oauth_uid
13
- Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received entity from Connec! webhook: Entity=#{entity_name}, Data=#{entity}")
14
- if organization.sync_enabled && organization.synchronized_entities[entity_instance_hash[:name].to_sym]
15
- external_client = Maestrano::Connector::Rails::External.get_client(organization)
16
-
17
- # Build expected input for consolidate_and_map_data
18
- if entity_instance_hash[:is_complex]
19
- mapped_entity = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.class.connec_entities_names.collect{|name| name.parameterize('_').pluralize == entity_name ? [name, [entity]] : [ name, []]}.flatten(1) ], Hash[ *entity_instance.class.external_entities_names.collect{|name| [ name, []]}.flatten(1) ], organization, {})
20
- else
21
- mapped_entity = entity_instance.consolidate_and_map_data([entity], [], organization, {})
22
- end
23
-
24
- entity_instance.push_entities_to_external(external_client, mapped_entity[:connec_entities], organization)
25
- end
26
-
27
- else
28
- Rails.logger.warn "Received notification from Connec! for unknown group or group without oauth: #{entity['group_id']} (tenant: #{params[:tenant]})"
29
- end
8
+
9
+ entity_instance_hash = find_entity_instance(entity_name)
10
+ next Rails.logger.info "Received notification from Connec! for unknow entity: #{entity_name}" unless entity_instance_hash
11
+
12
+ entity_instance = entity_instance_hash[:instance]
13
+
14
+ entities.each do |entity|
15
+ organization = Maestrano::Connector::Rails::Organization.find_by_uid_and_tenant(entity[:group_id], params[:tenant])
16
+ next Rails.logger.warn "Received notification from Connec! for unknown group or group without oauth: #{entity['group_id']} (tenant: #{params[:tenant]})" unless organization && organization.oauth_uid
17
+ next unless organization.sync_enabled && organization.synchronized_entities[entity_instance_hash[:name].to_sym]
18
+
19
+
20
+ Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Received entity from Connec! webhook: Entity=#{entity_name}, Data=#{entity}")
21
+ connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
22
+ external_client = Maestrano::Connector::Rails::External.get_client(organization)
23
+ last_synchronization = organization.last_successful_synchronization
24
+
25
+ entity_instance.before_sync(connec_client, external_client, last_synchronization, organization, {})
26
+ # Build expected input for consolidate_and_map_data
27
+ if entity_instance_hash[:is_complex]
28
+ mapped_entity = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.class.connec_entities_names.collect{|name| name.parameterize('_').pluralize == entity_name ? [name, [entity]] : [ name, []]}.flatten(1) ], Hash[ *entity_instance.class.external_entities_names.collect{|name| [ name, []]}.flatten(1) ], organization, {})
29
+ else
30
+ mapped_entity = entity_instance.consolidate_and_map_data([entity], [], organization, {})
30
31
  end
31
- else
32
- Rails.logger.info "Received notification from Connec! for unknow entity: #{entity_name}"
32
+ entity_instance.push_entities_to_external(external_client, mapped_entity[:connec_entities], organization)
33
+
34
+ entity_instance.after_sync(connec_client, external_client, last_synchronization, organization, {})
33
35
  end
34
36
  end
35
37
  rescue => e
@@ -40,7 +42,6 @@ class Maestrano::ConnecController < Maestrano::Rails::WebHookController
40
42
  end
41
43
 
42
44
 
43
-
44
45
  private
45
46
  def find_entity_instance(entity_name)
46
47
  Maestrano::Connector::Rails::Entity.entities_list.each do |entity_name_from_list|
@@ -3,22 +3,29 @@ module Maestrano::Connector::Rails
3
3
  queue_as :default
4
4
 
5
5
  # expected hash: {"external_entity_name1" => [entity1, entity2], "external_entity_name2" => []}
6
- def perform(organization, entities_hash)
6
+ def perform(organization, entities_hash, opts={})
7
7
  return unless organization.sync_enabled && organization.oauth_uid
8
8
 
9
9
  connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
10
+ external_client = Maestrano::Connector::Rails::External.get_client(organization)
11
+ last_synchronization = organization.last_successful_synchronization
10
12
 
11
13
  entities_hash.each do |external_entity_name, entities|
12
14
  if entity_instance_hash = find_entity_instance(external_entity_name)
13
- entity_instance = entity_instance_hash[:instance]
14
15
  next unless organization.synchronized_entities[entity_instance_hash[:name].to_sym]
16
+
17
+ entity_instance = entity_instance_hash[:instance]
18
+
19
+ entity_instance.before_sync(connec_client, external_client, last_synchronization, organization, opts)
20
+ # Build expected input for consolidate_and_map_data
15
21
  if entity_instance_hash[:is_complex]
16
22
  mapped_entities = entity_instance.consolidate_and_map_data(Hash[ *entity_instance.class.connec_entities_names.collect{|name| [ name, []]}.flatten(1) ], Hash[ *entity_instance.class.external_entities_names.collect{|name| name == external_entity_name ? [name, entities] : [ name, []]}.flatten(1) ], organization, {})
17
23
  else
18
24
  mapped_entities = entity_instance.consolidate_and_map_data([], entities, organization, {})
19
25
  end
20
-
21
26
  entity_instance.push_entities_to_connec(connec_client, mapped_entities[:external_entities], organization)
27
+
28
+ entity_instance.after_sync(connec_client, external_client, last_synchronization, organization, opts)
22
29
  else
23
30
  Rails.logger.warn "Called push to connec job with unknow entity: #{external_entity_name}"
24
31
  end
@@ -3,7 +3,7 @@ module Maestrano::Connector::Rails
3
3
  queue_as :default
4
4
 
5
5
  # Supported options:
6
- # * :forced => true synchronization has been triggered manually (for logging purposes only)
6
+ # * :forced => true synchronization has been triggered manually
7
7
  # * :only_entities => [person, tasks_list]
8
8
  # * :full_sync => true synchronization is performed without date filtering
9
9
  # * :connec_preemption => true|false : preemption is always|never given to connec in case of conflict (if not set, the most recently updated entity is kept)
@@ -17,8 +17,8 @@ module Maestrano::Connector::Rails
17
17
  end
18
18
 
19
19
  # Check if recovery mode: last 3 synchronizations have failed
20
- if Synchronization.where(organization_id: organization.id, status: 'ERROR').order(created_at: :desc).limit(3).count == 3 \
21
- && Synchronization.where(organization_id: organization.id).order(created_at: :desc).limit(1).first.updated_at > 1.day.ago
20
+ if !opts[:forced] && organization.last_three_synchronizations_failed? \
21
+ && organization.synchronizations.order(created_at: :desc).limit(1).first.updated_at > 1.day.ago
22
22
  ConnectorLogger.log('info', organization, "Synchronization skipped: Recovery mode (three previous synchronizations have failed)")
23
23
  return
24
24
  end
@@ -28,7 +28,7 @@ module Maestrano::Connector::Rails
28
28
  current_synchronization = Synchronization.create_running(organization)
29
29
 
30
30
  begin
31
- last_synchronization = Synchronization.where(organization_id: organization.id, status: 'SUCCESS', partial: false).order(updated_at: :desc).first
31
+ last_synchronization = organization.last_successful_synchronization
32
32
  connec_client = Maestrano::Connec::Client[organization.tenant].new(organization.uid)
33
33
  external_client = External.get_client(organization)
34
34
 
@@ -56,11 +56,13 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
56
56
  idmap = sub_entity_instance.class.find_idmap({connec_id: entity['id'], external_entity: external_entity_name.downcase, organization_id: organization.id})
57
57
 
58
58
  if idmap
59
- idmap.update(name: sub_entity_instance.class.object_name_from_connec_entity_hash(entity))
60
- if (!idmap.to_external) || idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
59
+ return nil if idmap.external_inactive || !idmap.to_external
60
+
61
+ if idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
61
62
  Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{sub_entity_instance.class.entity_name} : #{entity}")
62
63
  nil
63
64
  else
65
+ idmap.update(name: sub_entity_instance.class.object_name_from_connec_entity_hash(entity))
64
66
  {entity: sub_entity_instance.map_to(external_entity_name, entity, organization), idmap: idmap}
65
67
  end
66
68
  else
@@ -103,18 +105,23 @@ module Maestrano::Connector::Rails::Concerns::ComplexEntity
103
105
  idmap = sub_entity_instance.class.find_idmap(external_id: sub_entity_instance.class.id_from_external_entity_hash(entity), connec_entity: connec_entity_name.downcase, organization_id: organization.id)
104
106
 
105
107
  # No idmap: creating one, nothing else to do
106
- if idmap
107
- idmap.update(name: sub_entity_instance.class.object_name_from_external_entity_hash(entity))
108
- else
108
+ unless idmap
109
109
  next {entity: sub_entity_instance.map_to(connec_entity_name, entity, organization), idmap: sub_entity_instance.class.create_idmap_from_external_entity(entity, connec_entity_name, organization)}
110
110
  end
111
111
 
112
112
  # Not pushing entity to Connec!
113
113
  next nil unless idmap.to_connec
114
114
 
115
+ # Not pushing to Connec! and flagging as inactive if inactive in external application
116
+ inactive = sub_entity_instance.class.inactive_from_external_entity_hash?(entity)
117
+ idmap.update(external_inactive: inactive)
118
+ next nil if inactive
119
+
115
120
  # Entity has not been modified since its last push to connec!
116
121
  next nil if Maestrano::Connector::Rails::Entity.not_modified_since_last_push_to_connec?(idmap, entity, sub_entity_instance, organization)
117
122
 
123
+ idmap.update(name: sub_entity_instance.class.object_name_from_external_entity_hash(entity))
124
+
118
125
  # Check for conflict with entities from connec!
119
126
  equivalent_connec_entities = modeled_connec_entities[connec_entity_name][external_entity_name] || []
120
127
  Maestrano::Connector::Rails::Entity.solve_conflict(entity, sub_entity_instance, equivalent_connec_entities, connec_entity_name, idmap, organization, opts)
@@ -75,6 +75,21 @@ module Maestrano::Connector::Rails::Concerns::Entity
75
75
  raise "Not implemented"
76
76
  end
77
77
 
78
+ # Return a string representing the object from a connec! entity hash
79
+ def object_name_from_connec_entity_hash(entity)
80
+ raise "Not implemented"
81
+ end
82
+
83
+ # Return a string representing the object from an external entity hash
84
+ def object_name_from_external_entity_hash(entity)
85
+ raise "Not implemented"
86
+ end
87
+
88
+ # Returns a boolean
89
+ # Returns true is the entity is flagged as inactive (deleted) in the external application
90
+ def inactive_from_external_entity_hash?(entity)
91
+ false
92
+ end
78
93
  # ----------------------------------------------
79
94
  # Entity specific methods
80
95
  # Those methods need to be define in each entity
@@ -99,19 +114,7 @@ module Maestrano::Connector::Rails::Concerns::Entity
99
114
  raise "Not implemented"
100
115
  end
101
116
 
102
- # Return a string representing the object from a connec! entity hash
103
- def object_name_from_connec_entity_hash(entity)
104
- raise "Not implemented"
105
- end
106
-
107
- # Return a string representing the object from an external entity hash
108
- def object_name_from_external_entity_hash(entity)
109
- raise "Not implemented"
110
- end
111
-
112
117
  # [{reference_class: Entities::.., connec_field: 'account_id', external_field: 'account/something/id'}]
113
- # ledger_account_idmap = Entities::Account.find_idmap({connec_id: entity['account_id'], organization_id: organization.id})
114
- # ledger_account_id = ledger_account_idmap && ledger_account_idmap.external_id
115
118
  def references
116
119
  []
117
120
  end
@@ -286,11 +289,13 @@ module Maestrano::Connector::Rails::Concerns::Entity
286
289
  idmap = self.class.find_idmap({connec_id: entity['id'], organization_id: organization.id})
287
290
 
288
291
  if idmap
289
- idmap.update(name: self.class.object_name_from_connec_entity_hash(entity))
290
- if (!idmap.to_external) || (idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at'])
292
+ return nil if idmap.external_inactive || !idmap.to_external
293
+
294
+ if idmap.last_push_to_external && idmap.last_push_to_external > entity['updated_at']
291
295
  Maestrano::Connector::Rails::ConnectorLogger.log('info', organization, "Discard Connec! #{self.class.connec_entity_name} : #{entity}")
292
296
  nil
293
297
  else
298
+ idmap.update(name: self.class.object_name_from_connec_entity_hash(entity))
294
299
  {entity: map_to_external(entity, organization), idmap: idmap}
295
300
  end
296
301
  else
@@ -360,28 +365,32 @@ module Maestrano::Connector::Rails::Concerns::Entity
360
365
 
361
366
  mapped_external_entities = external_entities.map{|entity|
362
367
  idmap = self.class.find_idmap({external_id: self.class.id_from_external_entity_hash(entity), organization_id: organization.id})
368
+
363
369
  # No idmap: creating one, nothing else to do
364
- if idmap
365
- idmap.update(name: self.class.object_name_from_external_entity_hash(entity))
366
- else
370
+ unless idmap
367
371
  next {entity: map_to_connec(entity, organization), idmap: self.class.create_idmap_from_external_entity(entity, organization)}
368
372
  end
369
373
 
370
374
  # Not pushing entity to Connec!
371
375
  next nil unless idmap.to_connec
372
376
 
377
+ # Not pushing to Connec! and flagging as inactive if inactive in external application
378
+ inactive = self.class.inactive_from_external_entity_hash?(entity)
379
+ idmap.update(external_inactive: inactive)
380
+ next nil if inactive
381
+
373
382
  # Entity has not been modified since its last push to connec!
374
383
  next nil if self.class.not_modified_since_last_push_to_connec?(idmap, entity, self, organization)
375
384
 
385
+ idmap.update(name: self.class.object_name_from_external_entity_hash(entity))
386
+
376
387
  # Check for conflict with entities from connec!
377
388
  self.class.solve_conflict(entity, self, connec_entities, self.class.connec_entity_name, idmap, organization, opts)
378
- }
379
- mapped_external_entities.compact!
389
+ }.compact
380
390
 
381
391
  mapped_connec_entities = connec_entities.map{|entity|
382
392
  map_to_external_with_idmap(entity, organization)
383
- }
384
- mapped_connec_entities.compact!
393
+ }.compact
385
394
 
386
395
  return {connec_entities: mapped_connec_entities, external_entities: mapped_external_entities}
387
396
  end
@@ -59,5 +59,14 @@ module Maestrano::Connector::Rails
59
59
  self.instance_url = auth.credentials.instance_url
60
60
  self.save!
61
61
  end
62
+
63
+ def last_three_synchronizations_failed?
64
+ arr = self.synchronizations.last(3).map(&:is_error?)
65
+ arr.count == 3 && arr.uniq == [true]
66
+ end
67
+
68
+ def last_successful_synchronization
69
+ self.synchronizations.where(status: 'SUCCESS', partial: false).order(updated_at: :desc).first
70
+ end
62
71
  end
63
72
  end
@@ -0,0 +1,5 @@
1
+ class AddInactiveToIdmaps < ActiveRecord::Migration
2
+ def change
3
+ add_column :id_maps, :external_inactive, :boolean, default: false
4
+ end
5
+ end
@@ -46,4 +46,10 @@ class Maestrano::Connector::Rails::Entity
46
46
  # e.g entity['last_update']
47
47
  end
48
48
 
49
+ def self.inactive_from_external_entity_hash?(entity)
50
+ # TODO
51
+ # This method return true is entity is inactive in the external application
52
+ # e.g entity['status'] == 'INACTIVE'
53
+ end
54
+
49
55
  end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: maestrano-connector-rails 0.4.1 ruby lib
5
+ # stub: maestrano-connector-rails 0.4.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "maestrano-connector-rails"
9
- s.version = "0.4.1"
9
+ s.version = "0.4.2"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Pierre Berard"]
14
- s.date = "2016-04-19"
14
+ s.date = "2016-04-29"
15
15
  s.description = "Maestrano is the next generation marketplace for SME applications. See https://maestrano.com for details."
16
16
  s.email = "pierre.berard@maestrano.com"
17
17
  s.executables = ["rails"]
@@ -62,6 +62,7 @@ Gem::Specification.new do |s|
62
62
  "db/migrate/20151122163449_create_id_maps.rb",
63
63
  "db/migrate/20160205132857_add_sync_enabled_to_organizations.rb",
64
64
  "db/migrate/20160215103120_add_name_to_id_map.rb",
65
+ "db/migrate/20160427112250_add_inactive_to_idmaps.rb",
65
66
  "lib/generators/connector/USAGE",
66
67
  "lib/generators/connector/complex_entity_generator.rb",
67
68
  "lib/generators/connector/install_generator.rb",
@@ -70,8 +70,10 @@ describe Maestrano::ConnecController, type: :controller do
70
70
  let!(:organization) { create(:organization, uid: group_id, oauth_uid: 'lala', sync_enabled: true, synchronized_entities: {contact_and_lead: true}) }
71
71
 
72
72
  it 'process the data and push them' do
73
+ expect_any_instance_of(Entities::ContactAndLead).to receive(:before_sync)
73
74
  expect_any_instance_of(Entities::ContactAndLead).to receive(:consolidate_and_map_data).with({"Lead"=>[entity]}, {}, organization, {}).and_return({})
74
75
  expect_any_instance_of(Entities::ContactAndLead).to receive(:push_entities_to_external)
76
+ expect_any_instance_of(Entities::ContactAndLead).to receive(:after_sync)
75
77
  subject
76
78
  end
77
79
  end
@@ -135,8 +137,10 @@ describe Maestrano::ConnecController, type: :controller do
135
137
  let!(:organization) { create(:organization, uid: group_id, oauth_uid: 'lala', sync_enabled: true, synchronized_entities: {person: true}) }
136
138
 
137
139
  it 'process the data and push them' do
140
+ expect_any_instance_of(Entities::Person).to receive(:before_sync)
138
141
  expect_any_instance_of(Entities::Person).to receive(:consolidate_and_map_data).with([entity], [], organization, {}).and_return({})
139
142
  expect_any_instance_of(Entities::Person).to receive(:push_entities_to_external)
143
+ expect_any_instance_of(Entities::Person).to receive(:after_sync)
140
144
  subject
141
145
  end
142
146
  end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20160215103120) do
14
+ ActiveRecord::Schema.define(version: 20160427120963) do
15
15
 
16
16
  create_table "id_maps", force: :cascade do |t|
17
17
  t.string "connec_id"
@@ -21,12 +21,13 @@ ActiveRecord::Schema.define(version: 20160215103120) do
21
21
  t.integer "organization_id"
22
22
  t.datetime "last_push_to_connec"
23
23
  t.datetime "last_push_to_external"
24
- t.datetime "created_at", null: false
25
- t.datetime "updated_at", null: false
24
+ t.datetime "created_at", null: false
25
+ t.datetime "updated_at", null: false
26
26
  t.boolean "to_connec", default: true
27
27
  t.boolean "to_external", default: true
28
28
  t.string "name"
29
29
  t.string "message"
30
+ t.boolean "external_inactive", default: false
30
31
  end
31
32
 
32
33
  add_index "id_maps", ["connec_id", "connec_entity", "organization_id"], name: "idmap_connec_index"
@@ -40,6 +41,7 @@ ActiveRecord::Schema.define(version: 20160215103120) do
40
41
  t.string "tenant"
41
42
  t.string "oauth_provider"
42
43
  t.string "oauth_uid"
44
+ t.string "oauth_name"
43
45
  t.string "oauth_token"
44
46
  t.string "refresh_token"
45
47
  t.string "instance_url"
@@ -78,6 +80,8 @@ ActiveRecord::Schema.define(version: 20160215103120) do
78
80
  t.string "first_name"
79
81
  t.string "last_name"
80
82
  t.string "email"
83
+ t.string "locale"
84
+ t.string "timezone"
81
85
  t.string "tenant"
82
86
  t.datetime "created_at", null: false
83
87
  t.datetime "updated_at", null: false
@@ -60,7 +60,7 @@ describe Maestrano::Connector::Rails::PushToConnecJob do
60
60
  end
61
61
  end
62
62
 
63
- describe 'with entities in syncrhonized entities' do
63
+ describe 'with entities in synchronized entities' do
64
64
 
65
65
  describe 'complex entity' do
66
66
  before { organization.update(synchronized_entities: {:"#{entity_name1}" => false, :"#{entity_name2}" => true})}
@@ -75,6 +75,12 @@ describe Maestrano::Connector::Rails::PushToConnecJob do
75
75
  expect_any_instance_of(Entities::Entity1).to_not receive(:consolidate_and_map_data)
76
76
  subject
77
77
  end
78
+
79
+ it 'calls before and after sync' do
80
+ expect_any_instance_of(Entities::Entity2).to receive(:before_sync)
81
+ expect_any_instance_of(Entities::Entity2).to receive(:after_sync)
82
+ subject
83
+ end
78
84
  end
79
85
 
80
86
  describe 'non complex entity' do
@@ -91,6 +97,13 @@ describe Maestrano::Connector::Rails::PushToConnecJob do
91
97
  expect_any_instance_of(Entities::Entity2).to_not receive(:consolidate_and_map_data)
92
98
  subject
93
99
  end
100
+
101
+ it 'calls before and after sync' do
102
+ allow_any_instance_of(Entities::Entity1).to receive(:consolidate_and_map_data).and_return({})
103
+ expect_any_instance_of(Entities::Entity1).to receive(:before_sync)
104
+ expect_any_instance_of(Entities::Entity1).to receive(:after_sync)
105
+ subject
106
+ end
94
107
  end
95
108
  end
96
109
  end
@@ -2,52 +2,72 @@ require 'spec_helper'
2
2
 
3
3
  describe Maestrano::Connector::Rails::SynchronizationJob do
4
4
  let(:organization) { create(:organization) }
5
- subject { Maestrano::Connector::Rails::SynchronizationJob.perform_now(organization, {}) }
5
+ let(:opts) { {} }
6
+ subject { Maestrano::Connector::Rails::SynchronizationJob.perform_now(organization, opts) }
7
+
8
+ def does_not_perform
9
+ expect_any_instance_of(Maestrano::Connector::Rails::SynchronizationJob).to_not receive(:sync_entity)
10
+ expect{ subject }.to change{ Maestrano::Connector::Rails::Synchronization.count }.by(0)
11
+ end
12
+
13
+ def performes
14
+ expect{ subject }.to change{ Maestrano::Connector::Rails::Synchronization.count }.by(1)
15
+ end
6
16
 
7
17
  describe 'perform' do
8
18
  context 'with sync_enabled set to false' do
9
- it 'does not creates a syncrhonization' do
10
- expect{ subject }.to change{ Maestrano::Connector::Rails::Synchronization.count }.by(0)
11
- end
12
-
13
- it 'does not calls sync entity' do
14
- expect_any_instance_of(Maestrano::Connector::Rails::SynchronizationJob).to_not receive(:sync_entity)
15
- subject
16
- end
19
+ it { does_not_perform }
17
20
  end
18
21
 
19
22
  context 'with sync_enabled set to true' do
20
23
  before {organization.update(sync_enabled: true)}
21
24
 
25
+ context 'with a sync still running for less than 30 minutes' do
26
+ let!(:running_sync) { create(:synchronization, organization: organization, status: 'RUNNING', created_at: 29.minutes.ago) }
27
+ it { does_not_perform }
28
+ end
29
+
30
+ context 'with a sync still running for more than 30 minutes' do
31
+ let!(:running_sync) { create(:synchronization, organization: organization, status: 'RUNNING', created_at: 31.minutes.ago) }
32
+ it { performes }
33
+ end
34
+
22
35
  describe 'recovery mode' do
23
- describe 'skipping' do
36
+ context 'three last sync failed and last sync less than 24 hours ago' do
24
37
  before {
25
38
  3.times do
26
- organization.synchronizations.create(status: 'ERROR')
39
+ organization.synchronizations.create(status: 'ERROR', created_at: 2.hour.ago)
27
40
  end
28
41
  }
42
+ it { does_not_perform }
29
43
 
30
- it 'skipped the sync if 3 failed sync' do
31
- expect{ subject }.to_not change{ Maestrano::Connector::Rails::Synchronization.count }
44
+ context 'synchronization is forced' do
45
+ let(:opts) { {forced: true} }
46
+ it { performes }
32
47
  end
33
48
  end
34
49
 
35
- describe 'not skipping' do
50
+ context 'three last sync failed and last sync more than 24 hours ago' do
36
51
  before {
37
52
  3.times do
38
53
  organization.synchronizations.create(status: 'ERROR', created_at: 2.day.ago, updated_at: 2.day.ago)
39
54
  end
40
55
  }
56
+ it { performes }
57
+ end
41
58
 
42
- it 'does not skip the sync if 3 failed sync but last sync more than a day ago' do
43
- expect{ subject }.to change{ Maestrano::Connector::Rails::Synchronization.count }.by(1)
44
- end
59
+ context 'three sync failed but last sync is successfull' do
60
+ before {
61
+ 3.times do
62
+ organization.synchronizations.create(status: 'ERROR', created_at: 2.hour.ago)
63
+ end
64
+ organization.synchronizations.create(status: 'SUCCESS', created_at: 1.hour.ago)
65
+ }
66
+ it { performes }
45
67
  end
46
68
  end
47
69
 
48
- it 'creates a synchronization' do
49
- expect{ subject }.to change{ Maestrano::Connector::Rails::Synchronization.count }.by(1)
50
- end
70
+ it { performes }
51
71
 
52
72
  it 'calls sync entity on all the organization synchronized entities set to true' do
53
73
  organization.synchronized_entities[organization.synchronized_entities.keys.first] = false
@@ -58,7 +78,7 @@ describe Maestrano::Connector::Rails::SynchronizationJob do
58
78
 
59
79
  context 'with options' do
60
80
  context 'with only_entities' do
61
- subject { Maestrano::Connector::Rails::SynchronizationJob.perform_now(organization, {only_entities: %w(people price)}) }
81
+ let(:opts) { {only_entities: %w(people price)} }
62
82
 
63
83
  it 'calls sync entity on the specified entities' do
64
84
  expect_any_instance_of(Maestrano::Connector::Rails::SynchronizationJob).to receive(:sync_entity).twice
@@ -80,6 +80,14 @@ describe Maestrano::Connector::Rails::ComplexEntity do
80
80
  expect(subject.map_to_external_with_idmap(entity, organization, external_name, sub_instance)).to be_nil
81
81
  end
82
82
  end
83
+
84
+ context 'when entity has an idmap with external_inactive set to true' do
85
+ let!(:idmap) { create(:idmap, organization: organization, connec_id: id, connec_entity: connec_name.downcase, external_inactive: true, external_entity: external_name.downcase) }
86
+
87
+ it 'discards the entity' do
88
+ expect(subject.map_to_external_with_idmap(entity, organization, external_name, sub_instance)).to be_nil
89
+ end
90
+ end
83
91
  end
84
92
 
85
93
  end
@@ -218,7 +226,7 @@ describe Maestrano::Connector::Rails::ComplexEntity do
218
226
  expect(mapped_entities).to eql(external_entities: {
219
227
  'sc_e1' => {'Connec1' => [{entity: mapped_entity1, idmap: Maestrano::Connector::Rails::IdMap.first}]},
220
228
  'ScE2' => {
221
- 'Connec1' => [{entity: mapped_entity1, idmap: Maestrano::Connector::Rails::IdMap.all[1]}],
229
+ 'Connec1' => [{entity: mapped_entity1, idmap: Maestrano::Connector::Rails::IdMap.second}],
222
230
  'connec2' => [{entity: mapped_entity2, idmap: Maestrano::Connector::Rails::IdMap.last}],
223
231
  }
224
232
  },
@@ -244,6 +252,75 @@ describe Maestrano::Connector::Rails::ComplexEntity do
244
252
  end
245
253
  end
246
254
 
255
+ describe 'external_inactive flagging' do
256
+
257
+ context 'idmaps external_inactive set to true' do
258
+ let!(:idmap1) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sc_e1', connec_entity: 'connec1', external_inactive: true) }
259
+ let!(:idmap21) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sce2', connec_entity: 'connec1', external_inactive: true) }
260
+ let!(:idmap22) { create(:idmap, organization: organization, external_id: id2, external_entity: 'sce2', connec_entity: 'connec2', external_inactive: true) }
261
+
262
+ context 'entities inactive' do
263
+ before {
264
+ allow(Entities::SubEntities::ScE2).to receive(:inactive_from_external_entity_hash?).and_return(true)
265
+ allow(Entities::SubEntities::ScE1).to receive(:inactive_from_external_entity_hash?).and_return(true)
266
+ }
267
+
268
+ it 'discards the entities' do
269
+ expect(subject.consolidate_and_map_data({}, external_hash, organization, opt)).to eql(external_entities: {
270
+ 'sc_e1' => {'Connec1' => []},
271
+ 'ScE2' => {
272
+ 'Connec1' => [],
273
+ 'connec2' => [],
274
+ }
275
+ },
276
+ connec_entities: {})
277
+ end
278
+ end
279
+
280
+ context 'entities active' do
281
+ before {
282
+ allow(Entities::SubEntities::ScE2).to receive(:inactive_from_external_entity_hash?).and_return(false)
283
+ allow(Entities::SubEntities::ScE1).to receive(:inactive_from_external_entity_hash?).and_return(false)
284
+ [idmap1, idmap21, idmap22].each{|i| i.update(last_push_to_connec: 1.second.ago)} #Used only to simplify specing
285
+ }
286
+
287
+ it 'updates the idmaps' do
288
+ subject.consolidate_and_map_data({}, external_hash, organization, opt)
289
+ expect(idmap1.reload.external_inactive).to be false
290
+ expect(idmap21.reload.external_inactive).to be false
291
+ expect(idmap22.reload.external_inactive).to be false
292
+ end
293
+ end
294
+ end
295
+
296
+ context 'idmaps external_inactive set to false' do
297
+ let!(:idmap1) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sc_e1', connec_entity: 'connec1', external_inactive: false) }
298
+ let!(:idmap21) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sce2', connec_entity: 'connec1', external_inactive: false) }
299
+ let!(:idmap22) { create(:idmap, organization: organization, external_id: id2, external_entity: 'sce2', connec_entity: 'connec2', external_inactive: false) }
300
+
301
+ context 'entities inactive' do
302
+ before {
303
+ allow(Entities::SubEntities::ScE2).to receive(:inactive_from_external_entity_hash?).and_return(true)
304
+ allow(Entities::SubEntities::ScE1).to receive(:inactive_from_external_entity_hash?).and_return(true)
305
+ }
306
+
307
+ it 'discards the entities and updates the idmaps' do
308
+ expect(subject.consolidate_and_map_data({}, external_hash, organization, opt)).to eql(external_entities: {
309
+ 'sc_e1' => {'Connec1' => []},
310
+ 'ScE2' => {
311
+ 'Connec1' => [],
312
+ 'connec2' => [],
313
+ }
314
+ },
315
+ connec_entities: {})
316
+ expect(idmap1.reload.external_inactive).to be true
317
+ expect(idmap21.reload.external_inactive).to be true
318
+ expect(idmap22.reload.external_inactive).to be true
319
+ end
320
+ end
321
+ end
322
+ end
323
+
247
324
  context 'when entities have idmaps with more recent last_push_to_connec' do
248
325
  let!(:idmap1) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sc_e1', connec_entity: 'connec1', last_push_to_connec: 1.second.ago) }
249
326
  let!(:idmap21) { create(:idmap, organization: organization, external_id: id1, external_entity: 'sce2', connec_entity: 'connec1', last_push_to_connec: 1.second.ago) }
@@ -474,6 +474,17 @@ describe Maestrano::Connector::Rails::Entity do
474
474
  expect(subject.map_to_external_with_idmap(entity, organization)).to be_nil
475
475
  end
476
476
  end
477
+
478
+ context 'when external_inactive is true' do
479
+ let(:entity) { {'id' => id, 'name' => 'John' } }
480
+ before {
481
+ idmap.update(external_inactive: true)
482
+ }
483
+
484
+ it 'discards the entity' do
485
+ expect(subject.map_to_external_with_idmap(entity, organization)).to be_nil
486
+ end
487
+ end
477
488
  end
478
489
 
479
490
  context 'when entity has no idmap' do
@@ -706,6 +717,39 @@ describe Maestrano::Connector::Rails::Entity do
706
717
  end
707
718
  end
708
719
 
720
+ describe 'inactive flagging' do
721
+ context 'when idmap is inactive' do
722
+ let!(:idmap) { create(:idmap, external_entity: external_name.downcase, connec_entity: connec_name.downcase, external_id: id, organization: organization, external_inactive: true) }
723
+
724
+ context 'when entity is inactive' do
725
+ before { allow(subject.class).to receive(:inactive_from_external_entity_hash?).and_return(true) }
726
+
727
+ it 'discards the entity' do
728
+ expect(subject.consolidate_and_map_data([], entities, organization)).to eql({connec_entities: [], external_entities: []})
729
+ end
730
+ end
731
+
732
+ context 'when entity is active' do
733
+ before { allow(subject.class).to receive(:inactive_from_external_entity_hash?).and_return(false) }
734
+
735
+ it 'set the idmap as active' do
736
+ subject.consolidate_and_map_data([], entities, organization)
737
+ expect(idmap.reload.external_inactive).to be false
738
+ end
739
+ end
740
+ end
741
+
742
+ context 'when idmap is active and entity is inactive' do
743
+ let!(:idmap) { create(:idmap, external_entity: external_name.downcase, connec_entity: connec_name.downcase, external_id: id, organization: organization, external_inactive: false) }
744
+ before { allow(subject.class).to receive(:inactive_from_external_entity_hash?).and_return(true) }
745
+
746
+ it 'set the idmap as inactive and discards the entity' do
747
+ expect(subject.consolidate_and_map_data([], entities, organization)).to eql({connec_entities: [], external_entities: []})
748
+ expect(idmap.reload.external_inactive).to be true
749
+ end
750
+ end
751
+ end
752
+
709
753
  context 'when entity has an idmap with a last_push_to_connec more recent than date' do
710
754
  let!(:idmap) { create(:idmap, connec_entity: connec_name.downcase, external_entity: external_name.downcase, external_id: id, organization: organization, last_push_to_connec: 2.minute.ago) }
711
755
 
@@ -102,6 +102,47 @@ describe Maestrano::Connector::Rails::Organization do
102
102
  describe 'from_omniauth' do
103
103
  #TODO
104
104
  end
105
+
106
+ describe 'last_three_synchronizations_failed?' do
107
+ it 'returns true when last three syncs are failed' do
108
+ 3.times do
109
+ subject.synchronizations.create(status: 'ERROR')
110
+ end
111
+ expect(subject.last_three_synchronizations_failed?).to be true
112
+ end
113
+
114
+ it 'returns false when on of the last three sync is success' do
115
+ subject.synchronizations.create(status: 'SUCCESS')
116
+ 2.times do
117
+ subject.synchronizations.create(status: 'ERROR')
118
+ end
119
+
120
+ expect(subject.last_three_synchronizations_failed?).to be false
121
+ end
122
+
123
+ it 'returns false when no sync' do
124
+ expect(subject.last_three_synchronizations_failed?).to be false
125
+ end
126
+
127
+ it 'returns false when less than three sync' do
128
+ 2.times do
129
+ subject.synchronizations.create(status: 'ERROR')
130
+ end
131
+
132
+ expect(subject.last_three_synchronizations_failed?).to be false
133
+ end
134
+ end
135
+
136
+ describe 'last_successful_synchronization' do
137
+ let!(:running_sync) { create(:synchronization, organization: subject, status: 'RUNNING') }
138
+ let!(:failed_sync) { create(:synchronization, organization: subject, status: 'ERROR') }
139
+ let!(:success_sync) { create(:synchronization, organization: subject, status: 'SUCCESS') }
140
+ let!(:success_sync2) { create(:synchronization, organization: subject, status: 'SUCCESS', updated_at: 3.hours.ago) }
141
+ let!(:partial) { create(:synchronization, organization: subject, status: 'SUCCESS', partial: true) }
142
+
143
+ it { expect(subject.last_successful_synchronization).to eql(success_sync) }
144
+ end
105
145
  end
106
146
 
147
+
107
148
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maestrano-connector-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pierre Berard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-19 00:00:00.000000000 Z
11
+ date: 2016-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: maestrano-rails
@@ -272,6 +272,7 @@ files:
272
272
  - db/migrate/20151122163449_create_id_maps.rb
273
273
  - db/migrate/20160205132857_add_sync_enabled_to_organizations.rb
274
274
  - db/migrate/20160215103120_add_name_to_id_map.rb
275
+ - db/migrate/20160427112250_add_inactive_to_idmaps.rb
275
276
  - lib/generators/connector/USAGE
276
277
  - lib/generators/connector/complex_entity_generator.rb
277
278
  - lib/generators/connector/install_generator.rb