ae_easy-core 0.0.3

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 (69) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/CODE_OF_CONDUCT.md +74 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +57 -0
  7. data/LICENSE +21 -0
  8. data/README.md +16 -0
  9. data/Rakefile +22 -0
  10. data/ae_easy-core.gemspec +49 -0
  11. data/doc/AeEasy.html +117 -0
  12. data/doc/AeEasy/Core.html +1622 -0
  13. data/doc/AeEasy/Core/Config.html +311 -0
  14. data/doc/AeEasy/Core/Exception.html +117 -0
  15. data/doc/AeEasy/Core/Exception/OutdatedError.html +135 -0
  16. data/doc/AeEasy/Core/Helper.html +117 -0
  17. data/doc/AeEasy/Core/Helper/Cookie.html +1070 -0
  18. data/doc/AeEasy/Core/Mock.html +282 -0
  19. data/doc/AeEasy/Core/Mock/FakeDb.html +2316 -0
  20. data/doc/AeEasy/Core/Mock/FakeExecutor.html +2226 -0
  21. data/doc/AeEasy/Core/Mock/FakeParser.html +275 -0
  22. data/doc/AeEasy/Core/Mock/FakeSeeder.html +269 -0
  23. data/doc/AeEasy/Core/Plugin.html +117 -0
  24. data/doc/AeEasy/Core/Plugin/CollectionVault.html +299 -0
  25. data/doc/AeEasy/Core/Plugin/ConfigBehavior.html +541 -0
  26. data/doc/AeEasy/Core/Plugin/ContextIntegrator.html +445 -0
  27. data/doc/AeEasy/Core/Plugin/InitializeHook.html +220 -0
  28. data/doc/AeEasy/Core/Plugin/Parser.html +259 -0
  29. data/doc/AeEasy/Core/Plugin/ParserBehavior.html +420 -0
  30. data/doc/AeEasy/Core/Plugin/Seeder.html +635 -0
  31. data/doc/AeEasy/Core/Plugin/SeederBehavior.html +282 -0
  32. data/doc/AeEasy/Core/SmartCollection.html +1386 -0
  33. data/doc/_index.html +329 -0
  34. data/doc/class_list.html +51 -0
  35. data/doc/css/common.css +1 -0
  36. data/doc/css/full_list.css +58 -0
  37. data/doc/css/style.css +496 -0
  38. data/doc/file.README.html +91 -0
  39. data/doc/file_list.html +56 -0
  40. data/doc/frames.html +17 -0
  41. data/doc/index.html +91 -0
  42. data/doc/js/app.js +292 -0
  43. data/doc/js/full_list.js +216 -0
  44. data/doc/js/jquery.js +4 -0
  45. data/doc/method_list.html +811 -0
  46. data/doc/top-level-namespace.html +110 -0
  47. data/lib/ae_easy/core.rb +241 -0
  48. data/lib/ae_easy/core/config.rb +25 -0
  49. data/lib/ae_easy/core/exception.rb +8 -0
  50. data/lib/ae_easy/core/exception/outdated_error.rb +9 -0
  51. data/lib/ae_easy/core/helper.rb +8 -0
  52. data/lib/ae_easy/core/helper/cookie.rb +209 -0
  53. data/lib/ae_easy/core/mock.rb +44 -0
  54. data/lib/ae_easy/core/mock/fake_db.rb +280 -0
  55. data/lib/ae_easy/core/mock/fake_executor.rb +207 -0
  56. data/lib/ae_easy/core/mock/fake_parser.rb +31 -0
  57. data/lib/ae_easy/core/mock/fake_seeder.rb +28 -0
  58. data/lib/ae_easy/core/plugin.rb +15 -0
  59. data/lib/ae_easy/core/plugin/collection_vault.rb +23 -0
  60. data/lib/ae_easy/core/plugin/config_behavior.rb +43 -0
  61. data/lib/ae_easy/core/plugin/context_integrator.rb +60 -0
  62. data/lib/ae_easy/core/plugin/initialize_hook.rb +17 -0
  63. data/lib/ae_easy/core/plugin/parser.rb +19 -0
  64. data/lib/ae_easy/core/plugin/parser_behavior.rb +39 -0
  65. data/lib/ae_easy/core/plugin/seeder.rb +34 -0
  66. data/lib/ae_easy/core/plugin/seeder_behavior.rb +21 -0
  67. data/lib/ae_easy/core/smart_collection.rb +236 -0
  68. data/lib/ae_easy/core/version.rb +6 -0
  69. metadata +225 -0
@@ -0,0 +1,44 @@
1
+ require 'ae_easy/core/mock/fake_db'
2
+ require 'ae_easy/core/mock/fake_executor'
3
+ require 'ae_easy/core/mock/fake_parser'
4
+ require 'ae_easy/core/mock/fake_seeder'
5
+
6
+ module AeEasy
7
+ module Core
8
+ module Mock
9
+ # Generate a context and message queue from a list of exposed methods.
10
+ #
11
+ # @param [Array] exposed_methods List of exposed methods.
12
+ #
13
+ # @example
14
+ # exposed_methods = [:boo, :bar]
15
+ # context, message_queue = AeEasy::Core::Mock.context_vars exposed_methods
16
+ # context.boo 1, 2
17
+ # context.bar 'A', 'B'
18
+ # context.bar '111', '222'
19
+ # message_queue
20
+ # # => [
21
+ # # [:boo, [1, 2]],
22
+ # # [:bar, ['A', 'B']],
23
+ # # [:bar, ['111', '222']]
24
+ # # ]
25
+ #
26
+ # @return [Array] `[context, message_queue]` being:
27
+ # * `context`: Object implementing exposed methods.
28
+ # * `[Array] message_queue`: Array to store messages.
29
+ def self.context_vars exposed_methods
30
+ context = Object.new
31
+ metaclass = class << context; self; end
32
+ message_queue = [] # Beat reference bug
33
+ exposed_methods = exposed_methods
34
+ exposed_methods.each do |key|
35
+ metaclass.send(:define_method, key) do |*args|
36
+ # Record all method calls into message queue for easy access
37
+ message_queue << [key, args]
38
+ end
39
+ end
40
+ [context, message_queue]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,280 @@
1
+ module AeEasy
2
+ module Core
3
+ module Mock
4
+ # Fake in memory database that emulates `Answersengine` database objects' black box behavior.
5
+ class FakeDb
6
+ # Page id keys, analog to primary keys.
7
+ PAGE_KEYS = ['gid'].freeze
8
+ # Output id keys, analog to primary keys.
9
+ OUTPUT_KEYS = ['_id', '_collection'].freeze
10
+ # Default collection for saved outputs
11
+ DEFAULT_COLLECTION = 'default'
12
+
13
+ # Generate a smart collection with keys and initial values.
14
+ #
15
+ # @param [Array] keys Analog to primary keys, combination will be uniq.
16
+ # @param [Hash] opts Configuration options (see AeEasy::Core::SmartCollection#initialize).
17
+ #
18
+ # @return [AeEasy::Core::SmartCollection]
19
+ def self.new_collection keys, opts = {}
20
+ AeEasy::Core::SmartCollection.new keys, opts
21
+ end
22
+
23
+ # Generate a fake UUID.
24
+ #
25
+ # @param seed (nil) Object to use as seed for uuid.
26
+ #
27
+ # @return [String]
28
+ def self.fake_uuid seed = nil
29
+ seed ||= (Time.new.to_f + rand)
30
+ Digest::SHA1.hexdigest seed.to_s
31
+ end
32
+
33
+ # Build a page with defaults by using FakeDb engine.
34
+ #
35
+ # @param [Hash] page Page initial values.
36
+ # @param [Hash] opts ({}) Configuration options (see #initialize).
37
+ #
38
+ # @return [Hash]
39
+ def self.build_page page, opts = {}
40
+ temp_db = AeEasy::Core::Mock::FakeDb.new opts
41
+ temp_db.enable_page_gid_override
42
+ temp_db.pages << page
43
+ temp_db.pages.first
44
+ end
45
+
46
+ # Build a fake page by using FakeDb engine.
47
+ #
48
+ # @param [Hash] opts ({}) Configuration options (see #initialize).
49
+ # @option opts [String] :url ('https://example.com') Page url.
50
+ #
51
+ # @return [Hash]
52
+ def self.build_fake_page opts = {}
53
+ page = {
54
+ 'url' => (opts[:url] || 'https://example.com')
55
+ }
56
+ build_page page, opts
57
+ end
58
+
59
+ # Fake job id.
60
+ # @return [Integer,nil]
61
+ def job_id
62
+ @job_id ||= rand(1000) + 1
63
+ end
64
+
65
+ # Set fake job id value.
66
+ def job_id= value
67
+ @job_id = value
68
+ end
69
+
70
+ # Current fake page gid.
71
+ # @return [Integer,nil]
72
+ def page_gid
73
+ @page_gid ||= self.class.fake_uuid
74
+ end
75
+
76
+ # Set current fake page gid value.
77
+ def page_gid= value
78
+ @page_gid = value
79
+ end
80
+
81
+ # Enable page gid override on page insert.
82
+ def enable_page_gid_override
83
+ @allow_page_gid_override = true
84
+ end
85
+
86
+ # Disable page gid override on page insert.
87
+ def disable_page_gid_override
88
+ @allow_page_gid_override = false
89
+ end
90
+
91
+ # Specify whenever page gid overriding by user is allowed on page
92
+ # insert.
93
+ #
94
+ # @return [Boolean] `true` when allowed, else `false`.
95
+ def allow_page_gid_override?
96
+ @allow_page_gid_override ||= false
97
+ end
98
+
99
+ # Initialize fake database.
100
+ #
101
+ # @param [Hash] opts ({}) Configuration options.
102
+ # @option opts [Integer,nil] :job_id Job id default value.
103
+ # @option opts [String,nil] :page_gid Page gid default value.
104
+ # @option opts [Boolean, nil] :allow_page_gid_override (false) Specify
105
+ # whenever page gid can be overrided on page insert.
106
+ def initialize opts = {}
107
+ self.job_id = opts[:job_id]
108
+ self.page_gid = opts[:page_gid]
109
+ @allow_page_gid_override = opts[:allow_page_gid_override].nil? ? false : !!opts[:allow_page_gid_override]
110
+ end
111
+
112
+ # Generate a fake UUID based on page data:
113
+ # * url
114
+ # * method
115
+ # * headers
116
+ # * fetch_type
117
+ # * cookie
118
+ # * no_redirect
119
+ # * body
120
+ # * ua_type
121
+ #
122
+ # @param [Hash] data Output data.
123
+ #
124
+ # @return [String]
125
+ def generate_page_gid data
126
+ fields = [
127
+ 'url',
128
+ 'method',
129
+ 'headers',
130
+ 'fetch_type',
131
+ 'cookie',
132
+ 'no_redirect',
133
+ 'body',
134
+ 'ua_type'
135
+ ]
136
+ seed = data.select{|k,v|fields.include? k}.hash
137
+ self.class.fake_uuid seed
138
+ end
139
+
140
+ # Get page keys with key generators to emulate saving on db.
141
+ # @private
142
+ #
143
+ # @return [Hash]
144
+ def page_defaults
145
+ @page_keys ||= {
146
+ 'url' => nil,
147
+ 'method' => 'GET',
148
+ 'headers' => {},
149
+ 'fetch_type' => 'standard',
150
+ 'cookie' => nil,
151
+ 'no_redirect' => false,
152
+ 'body' => nil,
153
+ 'ua_type' => 'desktop',
154
+ 'vars' => {}
155
+ }
156
+ end
157
+
158
+ # Stored page collection.
159
+ #
160
+ # @return [AeEasy::Core::SmartCollection]
161
+ #
162
+ # @note Page gid will be replaced on insert by an auto generated uuid
163
+ # unless page gid overriding is enabled
164
+ # (see #allow_page_gid_override?)
165
+ def pages
166
+ return @pages unless @page.nil?
167
+
168
+ collection = self.class.new_collection PAGE_KEYS,
169
+ defaults: page_defaults
170
+ collection.bind_event(:before_defaults) do |collection, raw_item|
171
+ AeEasy::Core.deep_stringify_keys raw_item
172
+ end
173
+ collection.bind_event(:before_insert) do |collection, item, match|
174
+ if item['gid'].nil? || !allow_page_gid_override?
175
+ item['gid'] = generate_page_gid item
176
+ end
177
+ item
178
+ end
179
+ @pages ||= collection
180
+ end
181
+
182
+ # Generate a fake UUID based on output fields without `_` prefix.
183
+ #
184
+ # @param [Hash] data Output data.
185
+ #
186
+ # @return [String]
187
+ def generate_output_id data
188
+ seed = data.select{|k,v|k.to_s =~ /^[^_]/}.hash
189
+ self.class.fake_uuid seed
190
+ end
191
+
192
+ # Get output keys with key generators to emulate saving on db.
193
+ # @private
194
+ #
195
+ # @return [Hash]
196
+ def output_defaults
197
+ @output_keys ||= {
198
+ '_collection' => DEFAULT_COLLECTION,
199
+ '_job_id' => lambda{|output| job_id},
200
+ '_created_at' => lambda{|output| Time.new.strftime('%Y-%m-%dT%H:%M:%SZ')},
201
+ '_gid' => lambda{|output| page_gid}
202
+ }
203
+ end
204
+
205
+ # Stored output collection
206
+ #
207
+ # @return [AeEasy::Core::SmartCollection]
208
+ def outputs
209
+ return @outputs unless @outputs.nil?
210
+ collection = self.class.new_collection OUTPUT_KEYS,
211
+ defaults: output_defaults
212
+ collection.bind_event(:before_defaults) do |collection, raw_item|
213
+ AeEasy::Core.deep_stringify_keys raw_item
214
+ end
215
+ collection.bind_event(:before_insert) do |collection, item, match|
216
+ item['_id'] ||= generate_output_id item
217
+ item
218
+ end
219
+ @outputs ||= collection
220
+ end
221
+
222
+ # Match data to filters.
223
+ # @private
224
+ #
225
+ # @param data Hash containing data.
226
+ # @param filters Filters to apply on match.
227
+ #
228
+ # @return [Boolean]
229
+ #
230
+ # @note Missing and `nil` values on `data` will match when `filters`'
231
+ # field is `nil`.
232
+ def match? data, filters
233
+ filters.each do |key, value|
234
+ return false if data[key] != value
235
+ end
236
+ true
237
+ end
238
+
239
+ # Search items from a collection.
240
+ #
241
+ # @param [Symbol] collection Allowed values: `:outputs`, `:pages`.
242
+ # @param [Hash] filter Filters to query.
243
+ # @param [Integer] offset (0) Search results offset.
244
+ # @param [Integer|nil] limit (nil) Limit search results count. Set to `nil` for unlimited.
245
+ #
246
+ # @raise ArgumentError On unknown collection.
247
+ #
248
+ # @note _Warning:_ It uses table scan to filter and should be used on test suites only.
249
+ def query collection, filter, offset = 0, limit = nil
250
+ return [] unless limit.nil? || limit > 0
251
+
252
+ # Get collection items
253
+ items = case collection
254
+ when :outputs
255
+ outputs
256
+ when :pages
257
+ pages
258
+ else
259
+ raise ArgumentError.new "Unknown collection #{collection}."
260
+ end
261
+
262
+ # Search items
263
+ count = 0
264
+ matches = []
265
+ items.each do |item|
266
+ next unless match? item, filter
267
+ count += 1
268
+
269
+ # Skip until offset
270
+ next unless offset < count
271
+ # Break on limit reach
272
+ break unless limit.nil? || matches.count < limit
273
+ matches << item
274
+ end
275
+ matches
276
+ end
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,207 @@
1
+ module AeEasy
2
+ module Core
3
+ module Mock
4
+ # Fake executor that emulates `AnswersEngine` executor.
5
+ module FakeExecutor
6
+ # Page content.
7
+ # @return [String,nil]
8
+ attr_accessor :content
9
+ # Failed page content.
10
+ # @return [String,nil]
11
+ attr_accessor :failed_content
12
+
13
+ include AnswersEngine::Plugin::ContextExposer
14
+
15
+ # Validate executor methods compatibility.
16
+ # @private
17
+ #
18
+ # @param [Array] source Answersengine executor method collection.
19
+ # @param [Array] fragment Fake executor method collection.
20
+ #
21
+ # @return [Hash]
22
+ # @raise [AeEasy::Core::Exception::OutdatedError] When missing methods.
23
+ def self.check_compatibility source, fragment
24
+ report = AeEasy::Core.analyze_compatibility source, fragment
25
+
26
+ unless report[:new].count < 1
27
+ # Warn when outdated
28
+ warn <<-LONGDESC.gsub(/^\s+/,'')
29
+ It seems answersengine has new unmapped methods, try updating
30
+ ae_easy-core gem or contacting gem maintainer to update it.
31
+ New methods: #{report[:new].join ', '}
32
+ LONGDESC
33
+ end
34
+
35
+ # Ensure no missing methods
36
+ unless report[:is_compatible]
37
+ message = <<-LONGDESC.gsub(/^\s+/,'')
38
+ There are missing methods! Check your answersengine gem version.
39
+ Missing methods: #{report[:missing].join ', '}
40
+ LONGDESC
41
+ raise AeEasy::Core::Exception::OutdatedError.new(message)
42
+ end
43
+
44
+ report
45
+ end
46
+
47
+ # Draft pages, usually get saved after execution.
48
+ # @return [Array]
49
+ def pages
50
+ @pages ||= []
51
+ end
52
+
53
+ # Draft outputs, usually get saved after execution.
54
+ # @return [Array]
55
+ def outputs
56
+ @outputs ||= []
57
+ end
58
+
59
+ # Remove all elements on pages.
60
+ # @private
61
+ def clear_draft_pages
62
+ @pages.clear
63
+ end
64
+
65
+ # Remove all elements on outputs.
66
+ # @private
67
+ def clear_draft_outputs
68
+ @outputs.clear
69
+ end
70
+
71
+ # Fake database to represent what it is saved.
72
+ def db
73
+ @db ||= AeEasy::Core::Mock::FakeDb.new
74
+ end
75
+
76
+ # Initialize object.
77
+ #
78
+ # @param [Hash] opts ({}) Configuration options.
79
+ # @option opts [Array] :pages (nil) Array to initialize pages, can be nil for empty.
80
+ # @option opts [Array] :outputs (nil) Array to initialize outputs, can be nil for empty.
81
+ # @option opts [Integer] :job_id (nil) A number to represent the job_id.
82
+ # @option opts [Hash] :page (nil) Current page.
83
+ #
84
+ # @raise [ArgumentError] When pages or outputs are not Array.
85
+ def initialize opts = {}
86
+ unless opts[:pages].nil? || opts[:pages].is_a?(Array)
87
+ raise ArgumentError.new "Pages must be an array."
88
+ end
89
+ @pages = opts[:pages]
90
+ unless opts[:outputs].nil? || opts[:outputs].is_a?(Array)
91
+ raise ArgumentError.new "Outputs must be an array."
92
+ end
93
+ @outputs = opts[:outputs]
94
+ self.job_id = opts[:job_id]
95
+ self.page = opts[:page]
96
+ end
97
+
98
+ # Fake job ID used by executor.
99
+ # @return [Integer,nil]
100
+ def job_id
101
+ db.job_id
102
+ end
103
+
104
+ # Set fake job id value.
105
+ def job_id= value
106
+ db.job_id = value
107
+ page['job_id'] = value
108
+ end
109
+
110
+ # Current page used by executor.
111
+ # @return [Hash,nil]
112
+ def page
113
+ @page ||= AeEasy::Core::Mock::FakeDb.build_fake_page job_id: job_id
114
+ end
115
+
116
+ # Set current page.
117
+ def page= value
118
+ unless value.nil?
119
+ value = AeEasy::Core::Mock::FakeDb.build_page value
120
+ self.job_id = value['job_id'] unless value['job_id'].nil?
121
+ value['job_id'] ||= job_id
122
+ db.page_gid = value['gid'] unless value['gid'].nil?
123
+ end
124
+ @page = value
125
+ end
126
+
127
+ # Retrive a list of saved pages. Drafted pages can be included.
128
+ def saved_pages
129
+ db.pages
130
+ end
131
+
132
+ # Retrive a list of saved outputs.
133
+ def saved_outputs
134
+ db.outputs
135
+ end
136
+
137
+ # Save a page collection on db.
138
+ #
139
+ # @param [Array] list Collection of pages to save.
140
+ def save_pages list
141
+ list.each{|page| db.pages << page}
142
+ end
143
+
144
+ # Save an output collection on db.
145
+ #
146
+ # @param [Array] list Collection of outputs to save.
147
+ def save_outputs list
148
+ list.each{|output| db.outputs << output}
149
+ end
150
+
151
+ # Save draft pages into db and clear draft queue.
152
+ def flush_pages
153
+ save_pages pages
154
+ clear_draft_pages
155
+ end
156
+
157
+ # Save draft outputs into db and clear draft queue.
158
+ def flush_outputs
159
+ save_outputs outputs
160
+ clear_draft_outputs
161
+ end
162
+
163
+ # Save all drafts into db and clear draft queues.
164
+ def flush
165
+ flush_pages
166
+ flush_outputs
167
+ end
168
+
169
+ # Find outputs by collection and query with pagination.
170
+ #
171
+ # @param [String] collection ('default') Collection name.
172
+ # @param [Hash] query ({}) Filters to query.
173
+ # @param [Integer] page (1) Page number.
174
+ # @param [Integer] per_page (30) Page size.
175
+ #
176
+ # @return [Array]
177
+ def find_outputs collection = 'default', query = {}, page = 1, per_page = 30
178
+ count = 0
179
+ offset = (page - 1) * per_page
180
+ fixed_query = query.merge(
181
+ '_collection' => collection
182
+ )
183
+ db.query :outputs, fixed_query, offset, per_page
184
+ end
185
+
186
+ # Find one output by collection and query with pagination.
187
+ #
188
+ # @param [String] collection ('default') Collection name.
189
+ # @param [Hash] query ({}) Filters to query.
190
+ #
191
+ # @return [Hash, nil]
192
+ def find_output collection = 'default', query = {}
193
+ result = find_outputs(collection, query, 1, 1)
194
+ result.nil? ? nil : result.first
195
+ end
196
+
197
+ # Execute an script file as an executor.
198
+ #
199
+ # @param [String] file_path Script file path to execute.
200
+ def execute_script file_path, vars = {}
201
+ eval(File.read(file_path), isolated_binding(vars), file_path)
202
+ flush
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end