ae_easy-core 0.0.3

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