xip 0.0.1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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