xip 0.0.1 → 2.0.0.beta2

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +116 -0
  3. data/.gitignore +12 -0
  4. data/CHANGELOG.md +135 -0
  5. data/Gemfile +4 -1
  6. data/Gemfile.lock +65 -15
  7. data/LICENSE +6 -4
  8. data/README.md +51 -1
  9. data/VERSION +1 -0
  10. data/bin/xip +3 -11
  11. data/lib/xip.rb +1 -3
  12. data/lib/xip/base.rb +189 -0
  13. data/lib/xip/cli.rb +273 -0
  14. data/lib/xip/cli_base.rb +24 -0
  15. data/lib/xip/commands/command.rb +13 -0
  16. data/lib/xip/commands/console.rb +74 -0
  17. data/lib/xip/commands/server.rb +63 -0
  18. data/lib/xip/configuration.rb +56 -0
  19. data/lib/xip/controller/callbacks.rb +63 -0
  20. data/lib/xip/controller/catch_all.rb +84 -0
  21. data/lib/xip/controller/controller.rb +274 -0
  22. data/lib/xip/controller/dev_jumps.rb +40 -0
  23. data/lib/xip/controller/dynamic_delay.rb +61 -0
  24. data/lib/xip/controller/helpers.rb +128 -0
  25. data/lib/xip/controller/interrupt_detect.rb +99 -0
  26. data/lib/xip/controller/messages.rb +283 -0
  27. data/lib/xip/controller/nlp.rb +49 -0
  28. data/lib/xip/controller/replies.rb +281 -0
  29. data/lib/xip/controller/unrecognized_message.rb +61 -0
  30. data/lib/xip/core_ext.rb +5 -0
  31. data/lib/xip/core_ext/numeric.rb +10 -0
  32. data/lib/xip/core_ext/string.rb +18 -0
  33. data/lib/xip/dispatcher.rb +68 -0
  34. data/lib/xip/errors.rb +55 -0
  35. data/lib/xip/flow/base.rb +69 -0
  36. data/lib/xip/flow/specification.rb +56 -0
  37. data/lib/xip/flow/state.rb +82 -0
  38. data/lib/xip/generators/builder.rb +41 -0
  39. data/lib/xip/generators/builder/.gitignore +30 -0
  40. data/lib/xip/generators/builder/Gemfile +19 -0
  41. data/lib/xip/generators/builder/Procfile.dev +2 -0
  42. data/lib/xip/generators/builder/README.md +9 -0
  43. data/lib/xip/generators/builder/Rakefile +2 -0
  44. data/lib/xip/generators/builder/bot/controllers/bot_controller.rb +55 -0
  45. data/lib/xip/generators/builder/bot/controllers/catch_alls_controller.rb +21 -0
  46. data/lib/xip/generators/builder/bot/controllers/concerns/.keep +0 -0
  47. data/lib/xip/generators/builder/bot/controllers/goodbyes_controller.rb +9 -0
  48. data/lib/xip/generators/builder/bot/controllers/hellos_controller.rb +9 -0
  49. data/lib/xip/generators/builder/bot/controllers/interrupts_controller.rb +9 -0
  50. data/lib/xip/generators/builder/bot/controllers/unrecognized_messages_controller.rb +9 -0
  51. data/lib/xip/generators/builder/bot/helpers/bot_helper.rb +2 -0
  52. data/lib/xip/generators/builder/bot/models/bot_record.rb +3 -0
  53. data/lib/xip/generators/builder/bot/models/concerns/.keep +0 -0
  54. data/lib/xip/generators/builder/bot/replies/catch_alls/level1.yml +2 -0
  55. data/lib/xip/generators/builder/bot/replies/goodbyes/say_goodbye.yml +2 -0
  56. data/lib/xip/generators/builder/bot/replies/hellos/say_hello.yml +2 -0
  57. data/lib/xip/generators/builder/config.ru +4 -0
  58. data/lib/xip/generators/builder/config/boot.rb +6 -0
  59. data/lib/xip/generators/builder/config/database.yml +25 -0
  60. data/lib/xip/generators/builder/config/environment.rb +2 -0
  61. data/lib/xip/generators/builder/config/flow_map.rb +25 -0
  62. data/lib/xip/generators/builder/config/initializers/autoload.rb +8 -0
  63. data/lib/xip/generators/builder/config/initializers/inflections.rb +16 -0
  64. data/lib/xip/generators/builder/config/puma.rb +25 -0
  65. data/lib/xip/generators/builder/config/services.yml +35 -0
  66. data/lib/xip/generators/builder/config/sidekiq.yml +3 -0
  67. data/lib/xip/generators/builder/db/seeds.rb +7 -0
  68. data/lib/xip/generators/generate.rb +39 -0
  69. data/lib/xip/generators/generate/flow/controllers/controller.tt +7 -0
  70. data/lib/xip/generators/generate/flow/helpers/helper.tt +3 -0
  71. data/lib/xip/generators/generate/flow/replies/ask_example.tt +9 -0
  72. data/lib/xip/helpers/redis.rb +40 -0
  73. data/lib/xip/jobs.rb +9 -0
  74. data/lib/xip/lock.rb +82 -0
  75. data/lib/xip/logger.rb +9 -3
  76. data/lib/xip/migrations/configurator.rb +73 -0
  77. data/lib/xip/migrations/generators.rb +16 -0
  78. data/lib/xip/migrations/railtie_config.rb +14 -0
  79. data/lib/xip/migrations/tasks.rb +43 -0
  80. data/lib/xip/nlp/client.rb +21 -0
  81. data/lib/xip/nlp/result.rb +56 -0
  82. data/lib/xip/reloader.rb +89 -0
  83. data/lib/xip/reply.rb +36 -0
  84. data/lib/xip/scheduled_reply.rb +18 -0
  85. data/lib/xip/server.rb +63 -0
  86. data/lib/xip/service_message.rb +17 -0
  87. data/lib/xip/service_reply.rb +44 -0
  88. data/lib/xip/services/base_client.rb +24 -0
  89. data/lib/xip/services/base_message_handler.rb +27 -0
  90. data/lib/xip/services/base_reply_handler.rb +72 -0
  91. data/lib/xip/services/jobs/handle_message_job.rb +21 -0
  92. data/lib/xip/session.rb +203 -0
  93. data/lib/xip/version.rb +7 -1
  94. data/logo.svg +17 -0
  95. data/spec/configuration_spec.rb +93 -0
  96. data/spec/controller/callbacks_spec.rb +217 -0
  97. data/spec/controller/catch_all_spec.rb +154 -0
  98. data/spec/controller/controller_spec.rb +889 -0
  99. data/spec/controller/dynamic_delay_spec.rb +70 -0
  100. data/spec/controller/helpers_spec.rb +119 -0
  101. data/spec/controller/interrupt_detect_spec.rb +171 -0
  102. data/spec/controller/messages_spec.rb +744 -0
  103. data/spec/controller/nlp_spec.rb +93 -0
  104. data/spec/controller/replies_spec.rb +694 -0
  105. data/spec/controller/unrecognized_message_spec.rb +168 -0
  106. data/spec/dispatcher_spec.rb +79 -0
  107. data/spec/flow/flow_spec.rb +82 -0
  108. data/spec/flow/state_spec.rb +109 -0
  109. data/spec/helpers/redis_spec.rb +77 -0
  110. data/spec/lock_spec.rb +100 -0
  111. data/spec/nlp/client_spec.rb +23 -0
  112. data/spec/nlp/result_spec.rb +57 -0
  113. data/spec/replies/hello.yml.erb +15 -0
  114. data/spec/replies/messages/say_hola.yml+facebook.erb +6 -0
  115. data/spec/replies/messages/say_hola.yml+twilio.erb +6 -0
  116. data/spec/replies/messages/say_hola.yml.erb +6 -0
  117. data/spec/replies/messages/say_howdy_with_dynamic.yml +79 -0
  118. data/spec/replies/messages/say_msgs_without_breaks.yml +4 -0
  119. data/spec/replies/messages/say_offer.yml +6 -0
  120. data/spec/replies/messages/say_offer_with_dynamic.yml +6 -0
  121. data/spec/replies/messages/say_oi.yml.erb +15 -0
  122. data/spec/replies/messages/say_randomize_speech.yml +10 -0
  123. data/spec/replies/messages/say_randomize_text.yml +10 -0
  124. data/spec/replies/messages/say_yo.yml +6 -0
  125. data/spec/replies/messages/say_yo.yml+twitter +6 -0
  126. data/spec/replies/messages/sub1/sub2/say_nested.yml +10 -0
  127. data/spec/reply_spec.rb +61 -0
  128. data/spec/scheduled_reply_spec.rb +23 -0
  129. data/spec/service_reply_spec.rb +92 -0
  130. data/spec/session_spec.rb +366 -0
  131. data/spec/spec_helper.rb +22 -66
  132. data/spec/support/alternate_helpers/foo_helper.rb +5 -0
  133. data/spec/support/controllers/vaders_controller.rb +24 -0
  134. data/spec/support/helpers/fun/games_helper.rb +7 -0
  135. data/spec/support/helpers/fun/pdf_helper.rb +7 -0
  136. data/spec/support/helpers/standalone_helper.rb +5 -0
  137. data/spec/support/helpers_typo/users_helper.rb +2 -0
  138. data/spec/support/nlp_clients/dialogflow.rb +9 -0
  139. data/spec/support/nlp_clients/luis.rb +9 -0
  140. data/spec/support/nlp_results/luis_result.rb +163 -0
  141. data/spec/support/sample_messages.rb +66 -0
  142. data/spec/support/services.yml +31 -0
  143. data/spec/support/services_with_erb.yml +31 -0
  144. data/spec/version_spec.rb +16 -0
  145. data/xip.gemspec +25 -14
  146. metadata +320 -18
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ class ServiceReply
5
+
6
+ attr_accessor :recipient_id, :replies, :yaml_reply, :context
7
+
8
+ def initialize(recipient_id:, yaml_reply:, context:, preprocessor: :none)
9
+ @recipient_id = recipient_id
10
+ @yaml_reply = yaml_reply
11
+ @context = context
12
+
13
+ processed_reply = case preprocessor
14
+ when :erb
15
+ preprocess_erb
16
+ when :none
17
+ @yaml_reply
18
+ end
19
+
20
+ if yaml_reply.is_a?(Array)
21
+ @replies = load_replies(@yaml_reply)
22
+ else
23
+ @replies = load_replies(YAML.load(processed_reply))
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def load_replies(unstructured_replies)
30
+ unstructured_replies.collect do |reply|
31
+ Xip::Reply.new(unstructured_reply: reply)
32
+ end
33
+ end
34
+
35
+ def preprocess_erb
36
+ begin
37
+ ERB.new(yaml_reply).result(context)
38
+ rescue NameError => e
39
+ raise(Xip::Errors::UndefinedVariable, e.message)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xip/services/base_reply_handler'
4
+ require 'xip/services/base_message_handler'
5
+
6
+ require 'xip/services/jobs/handle_message_job'
7
+
8
+ module Xip
9
+ module Services
10
+ class BaseClient
11
+
12
+ attr_reader :reply
13
+
14
+ def initialize(reply:)
15
+ @reply = reply
16
+ end
17
+
18
+ def transmit
19
+ raise(Xip::Errors::ServiceImpaired, "Service implementation does not implement 'transmit'")
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ class BaseMessageHandler
6
+
7
+ attr_reader :params, :headers
8
+
9
+ def initialize(params:, headers:)
10
+ @params = params
11
+ @headers = headers
12
+ end
13
+
14
+ # Should respond with a Rack response (https://github.com/sinatra/sinatra#return-values)
15
+ def coordinate
16
+ raise(Xip::Errors::ServiceImpaired, "Service request handler does not implement 'process'")
17
+ end
18
+
19
+ # After coordinate responds to the service, an optional async job
20
+ # may be fired that will continue the work via this method
21
+ def process
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+ class BaseReplyHandler
6
+
7
+ attr_reader :recipient_id, :reply
8
+
9
+ def initialize(recipient_id:, reply:)
10
+ @recipient_id = recipient_id
11
+ @reply = reply
12
+ end
13
+
14
+ def text
15
+ reply_format_not_supported(format: 'text')
16
+ end
17
+
18
+ def image
19
+ reply_format_not_supported(format: 'image')
20
+ end
21
+
22
+ def audio
23
+ reply_format_not_supported(format: 'audio')
24
+ end
25
+
26
+ def video
27
+ reply_format_not_supported(format: 'video')
28
+ end
29
+
30
+ def file
31
+ reply_format_not_supported(format: 'file')
32
+ end
33
+
34
+ def cards
35
+ reply_format_not_supported(format: 'cards')
36
+ end
37
+
38
+ def list
39
+ reply_format_not_supported(format: 'list')
40
+ end
41
+
42
+ def receipt
43
+ reply_format_not_supported(format: 'receipt')
44
+ end
45
+
46
+ def mark_seen
47
+ reply_format_not_supported(format: 'mark_seen')
48
+ end
49
+
50
+ def enable_typing_indicator
51
+ reply_format_not_supported(format: 'enable_typing_indicator')
52
+ end
53
+
54
+ def disable_typing_indicator
55
+ reply_format_not_supported(format: 'disable_typing_indicator')
56
+ end
57
+
58
+ def delay
59
+ reply_format_not_supported(format: 'delay')
60
+ end
61
+
62
+ def speech
63
+ reply_format_not_supported(format: 'speech')
64
+ end
65
+
66
+ def ssml
67
+ reply_format_not_supported(format: 'ssml')
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ module Services
5
+
6
+ class HandleMessageJob < Xip::Jobs
7
+ sidekiq_options queue: :xip_webhooks, retry: false
8
+
9
+ def perform(service, params, headers)
10
+ dispatcher = Xip::Dispatcher.new(
11
+ service: service,
12
+ params: params,
13
+ headers: headers
14
+ )
15
+
16
+ dispatcher.process
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xip
4
+ class Session
5
+
6
+ include Xip::Redis
7
+
8
+ SLUG_SEPARATOR = '->'
9
+
10
+ attr_reader :flow, :state, :id, :type
11
+ attr_accessor :session
12
+
13
+ # Session types:
14
+ # - :primary
15
+ # - :previous
16
+ # - :back_to
17
+ def initialize(id: nil, type: :primary)
18
+ @id = id
19
+ @type = type
20
+
21
+ if id.present?
22
+ unless defined?($redis) && $redis.present?
23
+ raise(
24
+ Xip::Errors::RedisNotConfigured,
25
+ "Please make sure REDIS_URL is configured before using sessions"
26
+ )
27
+ end
28
+
29
+ get_session
30
+ end
31
+
32
+ self
33
+ end
34
+
35
+ def self.flow_and_state_from_session_slug(slug:)
36
+ {
37
+ flow: slug&.split(SLUG_SEPARATOR)&.first,
38
+ state: slug&.split(SLUG_SEPARATOR)&.last
39
+ }
40
+ end
41
+
42
+ def self.slugify(flow:, state:)
43
+ unless flow.present? && state.present?
44
+ raise(ArgumentError, 'A flow and state must be specified.')
45
+ end
46
+
47
+ [flow, state].join(SLUG_SEPARATOR)
48
+ end
49
+
50
+ def flow
51
+ return nil if flow_string.blank?
52
+
53
+ @flow ||= FlowMap.new.init(flow: flow_string, state: state_string)
54
+ end
55
+
56
+ def state
57
+ flow&.current_state
58
+ end
59
+
60
+ def flow_string
61
+ session&.split(SLUG_SEPARATOR)&.first
62
+ end
63
+
64
+ def state_string
65
+ session&.split(SLUG_SEPARATOR)&.last
66
+ end
67
+
68
+ def get_session
69
+ @session ||= get_key(session_key)
70
+ end
71
+
72
+ def set_session(new_flow:, new_state:)
73
+ @flow = nil # override @flow's memoization
74
+ existing_session = session # tmp backup for previous_session storage
75
+ @session = self.class.canonical_session_slug(
76
+ flow: new_flow,
77
+ state: new_state
78
+ )
79
+
80
+ Xip::Logger.l(
81
+ topic: [type, 'session'].join('_'),
82
+ message: "User #{id}: setting session to #{new_flow}->#{new_state}"
83
+ )
84
+
85
+ if primary_session?
86
+ store_current_to_previous(existing_session: existing_session)
87
+ end
88
+
89
+ persist_key(key: session_key, value: session)
90
+ end
91
+
92
+ def clear_session
93
+ $redis.del(session_key)
94
+ end
95
+
96
+ def present?
97
+ session.present?
98
+ end
99
+
100
+ def blank?
101
+ !present?
102
+ end
103
+
104
+ def +(steps)
105
+ return nil if flow.blank?
106
+ return self if steps.zero?
107
+
108
+ new_state = self.state + steps
109
+ new_session = Xip::Session.new(id: self.id)
110
+ new_session.session = self.class.canonical_session_slug(
111
+ flow: self.flow_string,
112
+ state: new_state
113
+ )
114
+
115
+ new_session
116
+ end
117
+
118
+ def -(steps)
119
+ return nil if flow.blank?
120
+
121
+ if steps < 0
122
+ return self + steps.abs
123
+ else
124
+ return self + (-steps)
125
+ end
126
+ end
127
+
128
+ def ==(other_session)
129
+ self.flow_string == other_session.flow_string &&
130
+ self.state_string == other_session.state_string &&
131
+ self.type == other_session.type &&
132
+ self.id == other_session.id
133
+ end
134
+
135
+ def self.is_a_session_string?(string)
136
+ session_regex = /(.+)(#{SLUG_SEPARATOR})(.+)/
137
+ !!string.match(session_regex)
138
+ end
139
+
140
+ def self.canonical_session_slug(flow:, state:)
141
+ [flow, state].join(SLUG_SEPARATOR)
142
+ end
143
+
144
+ def session_key
145
+ case type
146
+ when :primary
147
+ id
148
+ when :previous
149
+ previous_session_key
150
+ when :back_to
151
+ back_to_key
152
+ end
153
+ end
154
+
155
+ def primary_session?
156
+ type == :primary
157
+ end
158
+
159
+ def previous_session?
160
+ type == :previous
161
+ end
162
+
163
+ def back_to_session?
164
+ type == :back_to
165
+ end
166
+
167
+ def to_s
168
+ [flow_string, state_string].join(SLUG_SEPARATOR)
169
+ end
170
+
171
+ private
172
+
173
+ def previous_session_key
174
+ [id, 'previous'].join('-')
175
+ end
176
+
177
+ def back_to_key
178
+ [id, 'back_to'].join('-')
179
+ end
180
+
181
+ def store_current_to_previous(existing_session:)
182
+ # Prevent previous_session from becoming current_session
183
+ if session == existing_session
184
+ Xip::Logger.l(
185
+ topic: "previous_session",
186
+ message: "User #{id}: skipping setting to #{session}"\
187
+ ' because it is the same as current_session'
188
+ )
189
+ else
190
+ Xip::Logger.l(
191
+ topic: "previous_session",
192
+ message: "User #{id}: setting to #{existing_session}"
193
+ )
194
+
195
+ persist_key(
196
+ key: previous_session_key,
197
+ value: existing_session
198
+ )
199
+ end
200
+ end
201
+
202
+ end
203
+ end
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Xip
4
- VERSION = "0.0.1"
4
+ module Version
5
+ def self.version
6
+ File.read(File.join(File.dirname(__FILE__), '../..', 'VERSION')).strip
7
+ end
8
+ end
9
+
10
+ VERSION = Version.version
5
11
  end
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="925px" height="220px" viewBox="0 0 925 220" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>Dark bg</title>
4
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
5
+ <g id="Dark-bg">
6
+ <rect id="Rectangle" fill="#0D1117" x="0" y="0" width="925" height="220"></rect>
7
+ <path d="M629.337829,63 L629.33697,98.175 L629.375,98.1953125 L661.083984,63 L700.844727,63 L654.360352,112.472656 L703.999023,162.692383 L662.329102,162.692383 L629.375,126.334961 L629.33697,126.199 L629.337829,162.692383 L598.95697,162.692383 L598.95697,63 L629.337829,63 Z M747.441437,163 L747.441437,63.3076172 L717.060578,63.3076172 L717.060578,163 L747.441437,163 Z M838.098602,163 L838.098602,89.0400391 L881.760711,89.0400391 L881.760711,63.3076172 L764.055633,63.3076172 L764.055633,89.0400391 L807.717743,89.0400391 L807.717743,163 L838.098602,163 Z" id="KIT" fill="#9CA3AF" fill-rule="nonzero"></path>
8
+ <path d="M265.038178,163 L298.324311,126.642578 L331.278412,163 L372.948334,163 L323.309662,112.780273 L369.794037,63.3076172 L330.033295,63.3076172 L298.324311,98.5029297 L266.781342,63.3076172 L226.522553,63.3076172 L273.338959,112.780273 L223.949311,163 L265.038178,163 Z M416.28714,163 L416.28714,63.3076172 L385.906281,63.3076172 L385.906281,163 L416.28714,163 Z M470.333954,163 L470.333954,139.010742 L525.11911,139.010742 C528.384084,139.010742 531.593719,138.817057 534.748016,138.429688 C539.451792,137.931641 543.436167,136.631185 546.701141,134.52832 C554.72523,129.60319 558.764943,118.148112 558.820282,100.163086 C558.87562,94.0205078 558.183889,88.1546224 556.745086,82.5654297 C554.476206,73.2685547 548.720998,67.5133464 539.479461,65.2998047 C535.052378,64.2483724 530.182586,63.6396484 524.870086,63.4736328 L524.870086,63.4736328 L512.252899,63.3076172 L439.953094,63.3076172 L439.953094,163 L470.333954,163 Z M508.600555,113.27832 L470.333954,113.27832 L470.333954,89.0400391 L507.385507,89.0404626 C512.836071,89.0480857 516.94877,89.1586217 519.723602,89.3720703 C525.423472,89.7594401 528.328745,93.4394531 528.439422,100.412109 C528.439422,104.230469 527.77536,107.301758 526.447235,109.625977 C525.285126,111.728841 522.490529,112.890951 518.063446,113.112305 L518.063446,113.112305 L508.600555,113.27832 Z" id="XIP" fill="#9CA3AF" fill-rule="nonzero"></path>
9
+ <g id="Attempt-2" transform="translate(34.000000, 28.000000)" fill-rule="nonzero">
10
+ <path d="M35.5079177,0.152082297 L77.9343246,42.5784892 L77.8572,42.6550823 L77.7222,77.722 L42.585,77.858 L42.6852,77.7570823 L0.3292,35.4010823 L0.2292,35.502 L0.3647,0.3647 L35.4302,0.229082297 L35.5079177,0.152082297 Z" id="Combined-Shape" fill="#60A5FA"></path>
11
+ <path d="M135.585,0.2293 L170.722,0.3647 L170.858,35.4323 L170.933918,35.5079177 L128.507511,77.9343246 L128.43,77.8573 L93.365,77.722 L93.229,42.585 L93.329,42.6843 L135.685,0.3283 L135.585,0.2293 Z" id="Combined-Shape" fill="#34D399"></path>
12
+ <path d="M128.502,91.229 L128.401675,91.329 L170.757675,133.685 L170.858,133.585 L170.722,168.722 L135.653675,168.858 L135.579082,168.933918 L93.1526754,126.507511 L93.2286754,126.431 L93.365,91.365 L128.502,91.229 Z" id="Combined-Shape" fill="#60A5FA"></path>
13
+ <path d="M42.5789892,91.1526754 L42.6545823,91.2286754 L77.7222,91.365 L77.8576,126.502 L77.7575823,126.401675 L35.4015823,168.757675 L35.5019,168.858 L0.3647,168.722 L0.228582297,133.654675 L0.152582297,133.579082 L42.5789892,91.1526754 Z" id="Combined-Shape" fill="#34D399"></path>
14
+ </g>
15
+ </g>
16
+ </g>
17
+ </svg>
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe "Xip::Configuration" do
6
+
7
+ describe "accessing via method calling" do
8
+ let(:services_yml) { File.read(File.join(File.dirname(__FILE__), 'support', 'services.yml')) }
9
+ let(:parsed_config) { YAML.load(ERB.new(services_yml).result)[Xip.env] }
10
+ let(:config) { Xip.load_services_config!(services_yml) }
11
+
12
+ it "should return the root node" do
13
+ expect(config.facebook).to eq parsed_config['facebook']
14
+ end
15
+
16
+ it "should access deeply nested nodes" do
17
+ expect(config.facebook.setup.greeting).to eq parsed_config['facebook']['setup']['greeting']
18
+ end
19
+
20
+ it "should handle values that are arrays correctly" do
21
+ expect(config.facebook.setup.persistent_menu).to be_a(Array)
22
+ end
23
+
24
+ it "should retain the configuration at the class level" do
25
+ expect(Xip.config.facebook.setup.greeting).to eq parsed_config['facebook']['setup']['greeting']
26
+ end
27
+
28
+ it "should handle multiple keys at the root level" do
29
+ expect(config.twilio_sms.account_sid).to eq parsed_config['twilio_sms']['account_sid']
30
+ end
31
+
32
+ it "should return nil if the key is not present at the node" do
33
+ expect(config.twilio_sms.api_key).to be nil
34
+ end
35
+
36
+ it "should raise a NoMethodError when accessing multi-levels of missing nodes" do
37
+ expect { config.slack.api_key }.to raise_error(NoMethodError)
38
+ end
39
+ end
40
+
41
+ describe "config files with ERB" do
42
+ let(:services_yml) { File.read(File.join(File.dirname(__FILE__), 'support', 'services_with_erb.yml')) }
43
+ let(:config) { Xip.load_services_config!(services_yml) }
44
+
45
+ it "should replace available embedded env vars" do
46
+ ENV['FACEBOOK_VERIFY_TOKEN'] = 'it works'
47
+ expect(config.facebook.verify_token).to eq 'it works'
48
+ end
49
+
50
+ it "should replace unavailable embedded env vars with nil" do
51
+ expect(config.facebook.challenge).to be_nil
52
+ end
53
+
54
+ it "should not reload the configuration file if one already exists" do
55
+ Xip.load_services_config(services_yml)
56
+ expect(config.facebook.challenge).to be_nil
57
+ end
58
+ end
59
+
60
+ describe "configuring with default values" do
61
+ let(:config) {
62
+ Xip::Configuration.new(
63
+ { 'a' => nil, 'x' => 0, 'y' => false, 'z' => '' }
64
+ )
65
+ }
66
+
67
+ it 'should replace a nil value' do
68
+ config.set_default('a', 99)
69
+ expect(config.a).to eq 99
70
+ end
71
+
72
+ it 'should NOT replace a zero value' do
73
+ config.set_default('x', 99)
74
+ expect(config.x).to eq 0
75
+ end
76
+
77
+ it 'should NOT replace a false value' do
78
+ config.set_default('y', 99)
79
+ expect(config.y).to be false
80
+ end
81
+
82
+ it 'should NOT replace a blank string value' do
83
+ config.set_default('z', 99)
84
+ expect(config.z).to eq ''
85
+ end
86
+
87
+ it 'should replace a not-set key' do
88
+ config.set_default('zz', 99)
89
+ expect(config.zz).to eq 99
90
+ end
91
+ end
92
+
93
+ end