barkibu-kb-fake 0.16.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: da9fca49080eddaf77bbbb0f676b223922be9952c7cc8a4e715a43067fdd1f9e
4
+ data.tar.gz: 326523f469b2f65bfa8d3f2f2ee76746a0f6fffdb0dc261a5adbd18b820bddce
5
+ SHA512:
6
+ metadata.gz: bc9ee65490cfd754a6007d9bc30f911e7ae98f0fb8e487eb3bb3573ccc577b5cce6e26016fddbe05251bc6719a5c7d871c18f9264539c18c311bdf9dd617fc1c
7
+ data.tar.gz: 162af6023fc0f58f9571b81e562b9c1d501e90a1d7c00dde79d746b41ba6f64473dc2d183a235250e3de4fb863f88557c07ffacd12865d5e86f376db4389530c
@@ -0,0 +1 @@
1
+ require 'kb/fake'
@@ -0,0 +1,72 @@
1
+ require 'kb/fake/bounded_context/pet_family/breeds'
2
+ require 'kb/fake/bounded_context/pet_family/pet_parents'
3
+ require 'kb/fake/bounded_context/pet_family/pets'
4
+ require 'kb/fake/bounded_context/pet_family/products'
5
+ require 'kb/fake/bounded_context/pet_family/pet_contracts'
6
+ require 'kb/fake/bounded_context/pet_family/hubspot_relationship'
7
+
8
+ module KB
9
+ module Fake
10
+ class ApiState
11
+ attr_accessor :petparents, :pets, :consultations, :petcontracts, :plans, :breeds, :products, :hubspot_relationship
12
+
13
+ # rubocop:disable Metrics/ParameterLists
14
+ def initialize(petparents: [], pets: [], consultations: [], petcontracts: [], plans: [], breeds: [],
15
+ products: [], hubspot_relationship: [])
16
+ @petparents = petparents
17
+ @pets = pets
18
+ @consultations = consultations
19
+ @petcontracts = petcontracts
20
+ @plans = plans
21
+ @breeds = breeds
22
+ @products = products
23
+ @hubspot_relationship = hubspot_relationship
24
+ end
25
+ # rubocop:enable Metrics/ParameterLists
26
+
27
+ def to_snapshot
28
+ {
29
+ pets: @pets.clone,
30
+ petparents: @petparents.clone,
31
+ consultations: @consultations.clone,
32
+ petcontracts: @petcontracts.clone,
33
+ plans: @plans.clone,
34
+ breeds: @breeds.clone,
35
+ products: @products.clone,
36
+ hubspot_relationship: @hubspot_relationship.clone
37
+ }
38
+ end
39
+ end
40
+
41
+ class Api < Sinatra::Base
42
+ include BoundedContext::PetFamily::Breeds
43
+ include BoundedContext::PetFamily::Pets
44
+ include BoundedContext::PetFamily::PetParents
45
+ include BoundedContext::PetFamily::PetContracts
46
+ include BoundedContext::PetFamily::Products
47
+ include BoundedContext::PetFamily::HubspotRelationship
48
+
49
+ set :state, ApiState.new
50
+
51
+ def self.snapshot
52
+ Api.state.to_snapshot
53
+ end
54
+
55
+ def self.restore(snapshot)
56
+ set :state, ApiState.new(**snapshot)
57
+ end
58
+
59
+ def resource_state(name)
60
+ Api.state.send(name)
61
+ end
62
+
63
+ def set_resource_state(name, value)
64
+ Api.state.send("#{name}=", value)
65
+ end
66
+
67
+ resource :consultations, except: %i[create update destroy]
68
+
69
+ resource :plans, except: %i[show create update destroy]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+
3
+ module BoundedContext
4
+ module PetFamily
5
+ module Breeds
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include RestResource
10
+
11
+ listen_on_index :breeds, :v1
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+
3
+ module BoundedContext
4
+ module PetFamily
5
+ module HubspotRelationship
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include RestResource
10
+
11
+ get '/v1/hubspot/:model/:key/relationship' do
12
+ resource_by_key(:hubspot_relationship, params['key'])
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+
3
+ module BoundedContext
4
+ module PetFamily
5
+ module PetContracts
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include RestResource
10
+
11
+ get '/v1/petcontracts/contractnumber/:contract_number' do
12
+ resource = resource_state(:petcontracts).detect do |contract|
13
+ contract['contractNumber'] == params['contract_number']
14
+ end
15
+ return json_response 404, {} if resource.nil?
16
+
17
+ json_response 200, resource
18
+ end
19
+
20
+ resource :petcontracts, except: %i[index destroy]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,98 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+ # rubocop:disable Metrics/BlockLength
3
+
4
+ module BoundedContext
5
+ module PetFamily
6
+ module PetParents
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ include RestResource
11
+
12
+ resource :petparents
13
+
14
+ def petparents_filterable_attributes
15
+ KB::PetParent::FIELDS.map { |k| k.to_s.camelize(:lower) }
16
+ end
17
+
18
+ get '/v1/petparents/:key/pets' do
19
+ json_response 200, pets_by_pet_parent_key(params['key'])
20
+ end
21
+
22
+ get '/v1/petparents/:key/contracts' do
23
+ pet_keys = pets_by_pet_parent_key(params['key']).map { |pet| pet['key'] }
24
+ contracts = resource_state(:petcontracts).select { |contract| pet_keys.include? contract['petKey'] }
25
+
26
+ json_response 200, contracts
27
+ end
28
+
29
+ put '/v1/petparents' do
30
+ params = JSON.parse(request.body.read)
31
+ existing_pet_parent = pet_parent_by_key(params) || pet_parent_by_email(params) || pet_parent_by_phone(params)
32
+ resource = (existing_pet_parent || { 'key' => SecureRandom.uuid }).merge params
33
+
34
+ if existing_pet_parent.present?
35
+ if same_phone_number_but_different_email?(existing_pet_parent, params)
36
+ return json_response 422, { error: 'Unprocessable Entity', message: 'Email can not be overridden' }
37
+ end
38
+
39
+ if same_email_but_different_phone_number?(existing_pet_parent, params)
40
+ previous_pet_parent_by_phone = pet_parent_by_phone(params)
41
+ if previous_pet_parent_by_phone.present?
42
+ return json_response 409,
43
+ { error: 'ConflictError',
44
+ message: 'Duplicated pet parent: same partner, phoneNumber \
45
+ and phoneNumberPrefix' }
46
+ end
47
+ end
48
+
49
+ update_resource_state(:petparents, resource)
50
+ else
51
+ resource_state(:petparents) << resource
52
+ end
53
+
54
+ json_response 200, resource
55
+ end
56
+
57
+ private
58
+
59
+ def pet_parent_by_key(params)
60
+ find_resource(:petparents, params['key']) if params['key']
61
+ end
62
+
63
+ def pet_parent_by_phone(params)
64
+ matches_by_phone = (if params['phoneNumber']
65
+ filter_resources(:petparents,
66
+ params.slice('phoneNumber',
67
+ 'prefixPhoneNumber'))
68
+ end)
69
+ matches_by_phone.first if matches_by_phone&.count == 1
70
+ end
71
+
72
+ def pet_parent_by_email(params)
73
+ matches_by_email = (filter_resources(:petparents, params.slice('email')) if params['email'])
74
+ matches_by_email.first if matches_by_email&.count == 1
75
+ end
76
+
77
+ def pets_by_pet_parent_key(key)
78
+ resource_state(:pets).select { |pet| pet['petParentKey'] == key }
79
+ end
80
+
81
+ def same_email_but_different_phone_number?(previous, new)
82
+ (previous['email'] == new['email']) &&
83
+ ((previous['phoneNumber'] != new['phoneNumber']) ||
84
+ (previous['prefixPhoneNumber'] != new['prefixPhoneNumber']))
85
+ end
86
+
87
+ def same_phone_number_but_different_email?(previous, new_resource)
88
+ return false unless new_resource.key?('email')
89
+
90
+ (previous['phoneNumber'] == new_resource['phoneNumber']) &&
91
+ (previous['prefixPhoneNumber'] == new_resource['prefixPhoneNumber']) &&
92
+ (previous['email'] != new_resource['email'])
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,84 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+ require 'date'
3
+ # rubocop:disable Metrics/BlockLength
4
+
5
+ module BoundedContext
6
+ module PetFamily
7
+ module Pets
8
+ extend ActiveSupport::Concern
9
+ include RestResource
10
+
11
+ included do
12
+ include RestResource
13
+
14
+ resource :pets
15
+
16
+ def pets_filterable_attributes
17
+ KB::Pet::FIELDS.map { |k| k.to_s.camelize(:lower) }
18
+ end
19
+
20
+ def on_pets_create(_version)
21
+ resource = JSON.parse(request.body.read)
22
+ resource['ageCategory'] = stage(resource['birthDate'], resource['species'])
23
+ resource = resource.merge 'key' => SecureRandom.uuid
24
+ resource_state(:pets) << resource
25
+ json_response 201, resource
26
+ end
27
+
28
+ def on_pets_update(_version)
29
+ resource_to_update = find_resource :pets, params['key']
30
+
31
+ return json_response 404, {} if resource_to_update.nil?
32
+
33
+ partial_resource = JSON.parse(request.body.read)
34
+ partial_resource['ageCategory'] = stage(partial_resource['birthDate'], resource_to_update['species'])
35
+ updated_resource = resource_to_update.merge partial_resource
36
+
37
+ update_resource_state(:pets, updated_resource)
38
+
39
+ json_response 200, updated_resource
40
+ end
41
+
42
+ get '/v1/pets/:key/contracts' do
43
+ contracts = resource_state(:petcontracts).select { |contract| contract['petKey'] == params['key'] }
44
+
45
+ json_response 200, contracts
46
+ end
47
+
48
+ put '/v1/pets' do
49
+ params = JSON.parse(request.body.read)
50
+ pet_parent = find_resource(:petparents, params['petParentKey'])
51
+
52
+ return json_response 422, {} if pet_parent.nil?
53
+
54
+ potential_matches = filter_resources(:pets, params.slice('name', 'petParentKey'))
55
+ existing_pet = (potential_matches.first if potential_matches.count == 1)
56
+
57
+ resource = (existing_pet || { 'key' => SecureRandom.uuid }).merge params
58
+
59
+ if existing_pet.present?
60
+ update_resource_state(:pets, resource)
61
+ else
62
+ resource_state(:pets) << resource
63
+ end
64
+
65
+ json_response 200, resource
66
+ end
67
+ end
68
+
69
+ def stage(birthdate, species)
70
+ return nil if birthdate.nil?
71
+
72
+ case ((Time.zone.now - Time.zone.parse(birthdate)) / 1.month).to_i
73
+ when 0..11
74
+ species == 'cat' ? 'kitten' : 'puppy'
75
+ when 12..99
76
+ 'adult'
77
+ else
78
+ 'senior'
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,28 @@
1
+ require 'kb/fake/bounded_context/rest_resource'
2
+
3
+ module BoundedContext
4
+ module PetFamily
5
+ module Products
6
+ extend ActiveSupport::Concern
7
+ include RestResource
8
+
9
+ included do
10
+ include RestResource
11
+
12
+ resource :products, except: %i[create update destroy]
13
+
14
+ def products_filterable_attributes
15
+ [:country]
16
+ end
17
+
18
+ def on_products_index(_version)
19
+ return json_response 400, {} if params['country'].nil?
20
+
21
+ return json_response 422, {} if ISO3166::Country.search(params['country']).nil?
22
+
23
+ json_response 200, filter_resources(:products, params)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,134 @@
1
+ # rubocop:disable Metrics/BlockLength
2
+ module BoundedContext
3
+ module RestResource
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ def resource_by_key(resource, key)
8
+ entity = find_resource(resource, key)
9
+ return json_response 404, {} if entity.nil?
10
+
11
+ json_response 200, entity
12
+ end
13
+
14
+ def json_response(response_code, body_content)
15
+ content_type :json
16
+ status response_code
17
+ body body_content.to_json
18
+ end
19
+
20
+ def on_index_action(name, version)
21
+ return send("on_#{name}_index", version) if respond_to? "on_#{name}_index"
22
+
23
+ json_response 200, filter_resources(name, params)
24
+ end
25
+
26
+ def filter_resources(name, filters)
27
+ resource_state(name).select do |item|
28
+ item[:deleted_at].blank? && filters.slice(*filterable_attributes(name)).reduce(true) do |sum, (key, value)|
29
+ sum && (value.blank? \
30
+ || (item.fetch(key, '') || '').downcase.include?(value.downcase))
31
+ end
32
+ end
33
+ end
34
+
35
+ def filterable_attributes(name)
36
+ try("#{name}_filterable_attributes") || []
37
+ end
38
+
39
+ def on_show_action(name, version)
40
+ return send("on_#{name}_show", version) if respond_to? "on_#{name}_show"
41
+
42
+ resource_by_key name, params['key']
43
+ end
44
+
45
+ def on_create_action(name, version)
46
+ return send("on_#{name}_create", version) if respond_to? "on_#{name}_create"
47
+
48
+ resource = JSON.parse(request.body.read)
49
+ resource = resource.merge 'key' => SecureRandom.uuid
50
+ resource_state(name) << resource
51
+ json_response 201, resource
52
+ end
53
+
54
+ def on_update_action(name, version)
55
+ return send("on_#{name}_update", version) if respond_to? "on_#{name}_update"
56
+
57
+ resource_to_update = find_resource name, params['key']
58
+
59
+ return json_response 404, {} if resource_to_update.nil?
60
+
61
+ partial_resource = JSON.parse(request.body.read)
62
+ updated_resource = resource_to_update.merge partial_resource
63
+
64
+ update_resource_state(name, updated_resource)
65
+
66
+ json_response 200, updated_resource
67
+ end
68
+
69
+ def on_destroy_action(name, version)
70
+ return send("on_#{name}_destroy", version) if respond_to? "on_#{name}_destroy"
71
+
72
+ resource_to_delete = find_resource name, params['key']
73
+ resource_to_delete[:deleted_at] = DateTime.now
74
+
75
+ update_resource_state(name, resource_to_delete)
76
+
77
+ json_response 204, nil
78
+ end
79
+
80
+ private
81
+
82
+ def find_resource(name, key)
83
+ resource_state(name).detect { |resource| resource['key'] == key }
84
+ end
85
+
86
+ def update_resource_state(name, updated_resource)
87
+ updated_resources = resource_state(name).map do |resource|
88
+ resource['key'] == updated_resource['key'] ? updated_resource : resource
89
+ end
90
+
91
+ set_resource_state(name, updated_resources)
92
+ end
93
+ end
94
+
95
+ class_methods do
96
+ def listen_on_index(name, version)
97
+ get "/#{version}/#{name}" do
98
+ on_index_action(name, version)
99
+ end
100
+ end
101
+
102
+ def listen_on_show(name, version)
103
+ get "/#{version}/#{name}/:key" do
104
+ on_show_action(name, version)
105
+ end
106
+ end
107
+
108
+ def listen_on_create(name, version)
109
+ post "/#{version}/#{name}" do
110
+ on_create_action(name, version)
111
+ end
112
+ end
113
+
114
+ def listen_on_update(name, version)
115
+ patch "/#{version}/#{name}/:key" do
116
+ on_update_action(name, version)
117
+ end
118
+ end
119
+
120
+ def listen_on_destroy(name, version)
121
+ delete "/#{version}/#{name}/:key" do
122
+ on_destroy_action(name, version)
123
+ end
124
+ end
125
+
126
+ def resource(name, version: 'v1', except: [])
127
+ %i[index show create update destroy].each do |action|
128
+ send("listen_on_#{action}", name, version) unless except.include?(action)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ # rubocop:enable Metrics/BlockLength
data/lib/kb/fake.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'active_support'
2
+ require 'countries'
3
+ require 'sinatra'
4
+ require 'webmock'
5
+
6
+ require 'kb/fake/api'
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: barkibu-kb-fake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.16.1
5
+ platform: ruby
6
+ authors:
7
+ - Léo Figea
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-06-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: countries
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: barkibu-kb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.16.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.16.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: sinatra
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A fake API of Barkibu's Knowledge Base destined to be used in tests for
70
+ application using the kb-ruby gem.
71
+ email:
72
+ - leo@barkibu.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - lib/barkibu-kb-fake.rb
78
+ - lib/kb/fake.rb
79
+ - lib/kb/fake/api.rb
80
+ - lib/kb/fake/bounded_context/pet_family/breeds.rb
81
+ - lib/kb/fake/bounded_context/pet_family/hubspot_relationship.rb
82
+ - lib/kb/fake/bounded_context/pet_family/pet_contracts.rb
83
+ - lib/kb/fake/bounded_context/pet_family/pet_parents.rb
84
+ - lib/kb/fake/bounded_context/pet_family/pets.rb
85
+ - lib/kb/fake/bounded_context/pet_family/products.rb
86
+ - lib/kb/fake/bounded_context/rest_resource.rb
87
+ homepage: https://app.barkibu.com
88
+ licenses:
89
+ - MIT
90
+ metadata:
91
+ homepage_uri: https://app.barkibu.com
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '2.6'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 3.1.2
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Barkibu's Knowledge Base Fake API
111
+ test_files: []