marty 2.4.0 → 2.4.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 +5 -5
- data/Gemfile.lock +1 -1
- data/app/components/marty/api_auth_view.rb +4 -27
- data/app/components/marty/extras/layout.rb +7 -1
- data/app/components/marty/grid.rb +10 -9
- data/app/controllers/marty/rpc_controller.rb +4 -2
- data/app/models/marty/api_auth.rb +1 -80
- data/app/models/marty/api_config.rb +4 -4
- data/app/models/marty/delorean_rule.rb +2 -3
- data/config/routes.rb +1 -1
- data/db/migrate/{501_add_api_class_to_marty_api_config.rb → 500_add_api_class_to_marty_api_config.rb} +0 -0
- data/lib/marty/aws/base.rb +98 -0
- data/lib/marty/util.rb +15 -0
- data/lib/marty/version.rb +1 -1
- data/other/marty/api/base.rb +3 -0
- data/spec/controllers/job_controller_spec.rb +1 -1
- data/spec/dummy/app/components/gemini/xyz_rule_view.rb +0 -1
- data/spec/dummy/config/application.rb +0 -1
- data/spec/features/enum_spec.rb +100 -35
- data/spec/features/log_view_spec.rb +5 -5
- data/spec/features/rule_spec.rb +30 -30
- data/spec/features/user_view_spec.rb +2 -0
- data/spec/lib/logger_spec.rb +1 -1
- data/spec/models/event_spec.rb +1 -1
- data/spec/models/promise_spec.rb +1 -1
- data/spec/models/user_spec.rb +6 -6
- data/spec/spec_helper.rb +9 -69
- data/spec/support/chromedriver.rb +41 -0
- data/spec/support/components/netzke_combobox.rb +57 -0
- data/spec/support/components/netzke_grid.rb +356 -0
- data/spec/support/custom_matchers.rb +18 -0
- data/spec/support/custom_selectors.rb +49 -0
- data/spec/support/delayed_job_helpers.rb +4 -5
- data/spec/support/download_helper.rb +52 -0
- data/spec/support/helper.rb +20 -0
- data/spec/support/netzke.rb +306 -0
- data/spec/support/performance_helper.rb +26 -0
- data/spec/support/post_run_logger.rb +32 -0
- data/spec/support/{spec_setup.rb → setup.rb} +19 -6
- data/spec/support/shared_connection.rb +31 -0
- data/spec/support/{clean_db_helpers.rb → shared_connection_db_helpers.rb} +2 -2
- data/spec/support/structure_compare.rb +62 -0
- data/spec/support/suite.rb +27 -0
- data/spec/support/{integration_helpers.rb → users.rb} +11 -9
- metadata +20 -8
- data/db/migrate/502_add_parameters_to_marty_api_auth.rb +0 -5
- data/spec/support/user_helpers.rb +0 -12
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :netzke_include do |expected|
|
4
|
+
match do |actual|
|
5
|
+
parsed_values = actual.each_with_object({}) do | (k, v), h |
|
6
|
+
h[k] = v == "False" ? false : v
|
7
|
+
end
|
8
|
+
expect(parsed_values).to include(expected.stringify_keys)
|
9
|
+
end
|
10
|
+
|
11
|
+
diffable
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec::Matchers.define :match_fuzzily do |expected|
|
15
|
+
msg = nil
|
16
|
+
match { |actual| !(msg = struct_compare(actual, expected)) }
|
17
|
+
failure_message { |_| msg }
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Capybara.add_selector(:gridpanel) do
|
2
|
+
xpath do |name|
|
3
|
+
".//div[contains(@id, '#{name}')][not(contains(@id, 'splitter'))] | "\
|
4
|
+
".//div[contains(@id, '#{name.camelize(:lower)}')]"\
|
5
|
+
"[not(contains(@id, 'splitter'))]"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
Capybara.add_selector(:msg) do
|
10
|
+
xpath do
|
11
|
+
"//div[@id='msg-div']"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Capybara.add_selector(:body) do
|
16
|
+
xpath do
|
17
|
+
".//div[@data-ref='body']"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Capybara.add_selector(:input) do
|
22
|
+
xpath do |name|
|
23
|
+
"//input[@name='#{name}']"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Capybara.add_selector(:status) do
|
28
|
+
xpath do |name|
|
29
|
+
"//div[contains(@id, 'statusbar')]//div[text()='#{name}']"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Capybara.add_selector(:btn) do
|
34
|
+
xpath do |name|
|
35
|
+
".//span[text()='#{name}']"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Capybara.add_selector(:refresh) do
|
40
|
+
xpath do
|
41
|
+
".//div[contains(@class, 'x-tool-refresh')]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Capybara.add_selector(:gridcolumn) do
|
46
|
+
xpath do |name|
|
47
|
+
".//span[contains(@class, 'x-column-header')][text()='#{name}']/.."
|
48
|
+
end
|
49
|
+
end
|
@@ -1,12 +1,11 @@
|
|
1
|
-
module DelayedJobHelpers
|
1
|
+
module Marty::RSpec::DelayedJobHelpers
|
2
2
|
def start_delayed_job
|
3
|
-
#
|
4
|
-
`RAILS_ENV=test
|
5
|
-
`RAILS_ENV=test spec/dummy/script/delayed_job -n 4 start | cat`
|
3
|
+
`RAILS_ENV=test #{Rails.root}/script/delayed_job -n 4 stop | cat`
|
4
|
+
`RAILS_ENV=test #{Rails.root}/script/delayed_job -n 4 start | cat`
|
6
5
|
sleep 5
|
7
6
|
end
|
8
7
|
|
9
8
|
def stop_delayed_job
|
10
|
-
`RAILS_ENV=test
|
9
|
+
`RAILS_ENV=test #{Rails.root}/script/delayed_job -n 4 stop | cat`
|
11
10
|
end
|
12
11
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Marty::RSpec::DownloadHelper
|
2
|
+
TIMEOUT = 10
|
3
|
+
PATH = Rails.root.join('spec/tmp/downloads')
|
4
|
+
|
5
|
+
ACCEPTED_EXTS = ['.xlsx', '.csv']
|
6
|
+
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def downloads
|
10
|
+
Dir[PATH.join("*")]
|
11
|
+
end
|
12
|
+
|
13
|
+
def download
|
14
|
+
downloads.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def download_content
|
18
|
+
wait_for_download
|
19
|
+
# doesn't work for excel files...
|
20
|
+
File.read(download)
|
21
|
+
end
|
22
|
+
|
23
|
+
def download_content_acceptable?
|
24
|
+
wait_for_download
|
25
|
+
downloads.each do |f|
|
26
|
+
return false unless ACCEPTED_EXTS.include? File.extname(f)
|
27
|
+
end
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def wait_for_download
|
32
|
+
Timeout.timeout(TIMEOUT) do
|
33
|
+
sleep 0.1 until downloaded?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def downloaded?
|
38
|
+
downloads.any? && !downloading?
|
39
|
+
end
|
40
|
+
|
41
|
+
def downloading?
|
42
|
+
downloads.grep(/\.part$/).any? ||
|
43
|
+
downloads.select { |f| File.size(f).zero? }.any?
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_downloads
|
47
|
+
FileUtils.rm_f(downloads)
|
48
|
+
Timeout.timeout(TIMEOUT) do
|
49
|
+
sleep 0.1 until !downloads.any?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'delorean_lang'
|
2
|
+
|
3
|
+
module Marty; module RSpec;
|
4
|
+
class Helper
|
5
|
+
include Delorean::Model
|
6
|
+
# Helper function which increments a global counter. Can be used by
|
7
|
+
# tests which run Delorean code to see how many times some code is
|
8
|
+
# being called. Works for rule scripts as well.
|
9
|
+
delorean_fn :global_inc, sig: 1 do
|
10
|
+
|inc|
|
11
|
+
@@global_inc ||= 0
|
12
|
+
|
13
|
+
if inc
|
14
|
+
@@global_inc += inc
|
15
|
+
else
|
16
|
+
@@global_inc = 0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
module Marty; module RSpec; module Netzke
|
2
|
+
MAX_WAIT_TIME = 5.0
|
3
|
+
|
4
|
+
def by message, level=0
|
5
|
+
wait_for_ready(10)
|
6
|
+
pending(message) unless block_given?
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
|
10
|
+
alias and_by by
|
11
|
+
|
12
|
+
############################################################################
|
13
|
+
# navigation helpers
|
14
|
+
############################################################################
|
15
|
+
|
16
|
+
def ensure_on(path)
|
17
|
+
visit(path) unless current_path == path
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_in(username, password)
|
21
|
+
wait_for_ready(10)
|
22
|
+
|
23
|
+
begin
|
24
|
+
if first("a[data-qtip='Current user']")
|
25
|
+
log_out
|
26
|
+
wait_for_ajax
|
27
|
+
end
|
28
|
+
rescue
|
29
|
+
# ignore error
|
30
|
+
end
|
31
|
+
|
32
|
+
find(:xpath, "//span", text: 'Sign in', match: :first, wait: 5).click
|
33
|
+
fill_in("login", :with => username)
|
34
|
+
fill_in("password", :with => password)
|
35
|
+
press("OK")
|
36
|
+
wait_for_ajax
|
37
|
+
end
|
38
|
+
|
39
|
+
def log_in_as(username)
|
40
|
+
Rails.configuration.marty.auth_source = 'local'
|
41
|
+
|
42
|
+
ensure_on("/")
|
43
|
+
log_in(username, Rails.configuration.marty.local_password)
|
44
|
+
ensure_on("/")
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_out
|
48
|
+
press("Current user")
|
49
|
+
press("Sign out")
|
50
|
+
end
|
51
|
+
|
52
|
+
def press button_name, index_of = 0
|
53
|
+
wait_for_element do
|
54
|
+
begin
|
55
|
+
cmp = first("a[data-qtip='#{button_name}']")
|
56
|
+
cmp ||= first(:xpath, ".//a", text: "#{button_name}")
|
57
|
+
cmp ||= find(:btn, button_name, match: :first)
|
58
|
+
cmp.click
|
59
|
+
true
|
60
|
+
rescue
|
61
|
+
find_by_id(ext_button_id(button_name, index_of), visible: :all).click
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def popup message = ''
|
68
|
+
wait_for_ready
|
69
|
+
yield if block_given?
|
70
|
+
close_window
|
71
|
+
end
|
72
|
+
|
73
|
+
def close_window
|
74
|
+
find(:xpath, '//div[contains(@class, "x-tool-close")]', wait: 5).click
|
75
|
+
end
|
76
|
+
|
77
|
+
############################################################################
|
78
|
+
# stability functions
|
79
|
+
############################################################################
|
80
|
+
|
81
|
+
def wait_for_ready wait_time = nil
|
82
|
+
if wait_time
|
83
|
+
find(:status, 'Ready', wait: wait_time)
|
84
|
+
else
|
85
|
+
find(:status, 'Ready')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def wait_for_ajax wait_time = 10
|
90
|
+
wait_for_ready(wait_time)
|
91
|
+
wait_for_element { !ajax_loading? }
|
92
|
+
wait_for_ready
|
93
|
+
end
|
94
|
+
|
95
|
+
def ajax_loading?
|
96
|
+
page.execute_script <<-JS
|
97
|
+
return Netzke.ajaxIsLoading() || Ext.Ajax.isLoading();
|
98
|
+
JS
|
99
|
+
end
|
100
|
+
|
101
|
+
def wait_for_element(seconds_to_wait = 2.0, sleeptime = 0.1)
|
102
|
+
res = nil
|
103
|
+
start_time = current_time = Time.now
|
104
|
+
while !res && current_time - start_time < seconds_to_wait
|
105
|
+
begin
|
106
|
+
res = yield
|
107
|
+
rescue
|
108
|
+
ensure
|
109
|
+
sleep sleeptime
|
110
|
+
current_time = Time.now
|
111
|
+
end
|
112
|
+
end
|
113
|
+
res
|
114
|
+
end
|
115
|
+
|
116
|
+
############################################################################
|
117
|
+
# note that netzke_find doesn't actually find the component (as in Capybara)
|
118
|
+
# instead, it prepares the javascript to be run on the component object
|
119
|
+
############################################################################
|
120
|
+
|
121
|
+
def netzke_find(name, c_type = 'gridpanel')
|
122
|
+
case c_type
|
123
|
+
when 'combobox'
|
124
|
+
Marty::RSpec::Components::NetzkeCombobox.new(name)
|
125
|
+
else
|
126
|
+
Marty::RSpec::Components::NetzkeGrid.new(name, c_type)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def run_js js_str, seconds_to_wait = MAX_WAIT_TIME, sleeptime = 0.1
|
131
|
+
result = wait_for_element(seconds_to_wait, sleeptime) do
|
132
|
+
page.document.synchronize { @res = page.execute_script(js_str) }
|
133
|
+
@res
|
134
|
+
end
|
135
|
+
result
|
136
|
+
end
|
137
|
+
|
138
|
+
############################################################################
|
139
|
+
# component helpers
|
140
|
+
############################################################################
|
141
|
+
|
142
|
+
def show_submenu text
|
143
|
+
run_js <<-JS
|
144
|
+
Ext.ComponentQuery.query('menuitem[text="#{text}"] menu')[0].show()
|
145
|
+
JS
|
146
|
+
end
|
147
|
+
|
148
|
+
def ext_button_id title, scope = nil, index_of = 0
|
149
|
+
c_str = ext_arg('button{isVisible(true)}', text: "\"#{title}\"")
|
150
|
+
run_js <<-JS
|
151
|
+
return #{ext_find(c_str, scope, index_of)}.id;
|
152
|
+
JS
|
153
|
+
end
|
154
|
+
|
155
|
+
def set_field_value value, field_type='textfield', name=''
|
156
|
+
args1 = name.empty? ? "" : "[fieldLabel='#{name}']"
|
157
|
+
args2 = name.empty? ? "" : "[name='#{name}']"
|
158
|
+
run_js <<-JS
|
159
|
+
var field = Ext.ComponentQuery.query("#{field_type}#{args1}")[0];
|
160
|
+
field = field || Ext.ComponentQuery.query("#{field_type}#{args2}")[0];
|
161
|
+
field.setValue("#{value}");
|
162
|
+
return true;
|
163
|
+
JS
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_total_pages
|
167
|
+
# will get deprecated by Netzke 1.0
|
168
|
+
result = find(:xpath, ".//div[contains(@id, 'tbtext-')]",
|
169
|
+
text: /^of (\d+)$/, match: :first).text
|
170
|
+
result.split(' ')[1].to_i
|
171
|
+
end
|
172
|
+
|
173
|
+
############################################################################
|
174
|
+
# helpers
|
175
|
+
############################################################################
|
176
|
+
|
177
|
+
def id_of component
|
178
|
+
res = run_js <<-JS
|
179
|
+
var c = #{component};
|
180
|
+
return c.view.id;
|
181
|
+
JS
|
182
|
+
res
|
183
|
+
end
|
184
|
+
|
185
|
+
def btn_disabled? text
|
186
|
+
res = wait_for_element do
|
187
|
+
find_by_id(ext_button_id(text))
|
188
|
+
end
|
189
|
+
!res[:class].match(/disabled/).nil?
|
190
|
+
end
|
191
|
+
|
192
|
+
def click_checkbox(name)
|
193
|
+
q = %Q(checkbox[fieldLabel="#{name}"])
|
194
|
+
item_id = run_js "return Ext.ComponentQuery.query('#{q}')[0].getItemId();"
|
195
|
+
find_by_id(item_id).click
|
196
|
+
end
|
197
|
+
|
198
|
+
def paste text, textarea
|
199
|
+
# bit hacky: textarea doesn't like receiving tabs and newlines via fill_in
|
200
|
+
simple_escape!(text)
|
201
|
+
|
202
|
+
find(:xpath, ".//textarea[@name='#{textarea}']")
|
203
|
+
run_js <<-JS
|
204
|
+
#{ext_var(ext_find(ext_arg('textarea', name: textarea)), 'area')}
|
205
|
+
area.setValue("#{text}");
|
206
|
+
JS
|
207
|
+
end
|
208
|
+
|
209
|
+
def press_key_in(key, el_id)
|
210
|
+
kd = key.downcase
|
211
|
+
use_key = ['enter', 'return'].include?(kd) ? kd.to_sym : key
|
212
|
+
el = find_by_id("#{el_id}")
|
213
|
+
el.native.send_keys(use_key)
|
214
|
+
end
|
215
|
+
|
216
|
+
def simple_escape! text
|
217
|
+
text.gsub!(/(\r\n|\n)/, "\\n")
|
218
|
+
text.gsub!(/\t/, "\\t")
|
219
|
+
end
|
220
|
+
|
221
|
+
def simple_escape text
|
222
|
+
text.gsub(/(\r\n|\n)/, "\\n")
|
223
|
+
.gsub(/\t/, "\\t")
|
224
|
+
.gsub(/"/, '\"')
|
225
|
+
end
|
226
|
+
|
227
|
+
def type_in(type_s, el_id)
|
228
|
+
el = find_by_id("#{el_id}")
|
229
|
+
el.native.clear()
|
230
|
+
type_s.each_char do |key|
|
231
|
+
el.native.send_keys(key)
|
232
|
+
end
|
233
|
+
el.send_keys(:enter)
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
############################################################################
|
238
|
+
# ExtJS/Netzke helper javascripts:
|
239
|
+
# Netzke component lookups, arguments for helper methods
|
240
|
+
# (i.e. component) require JS scripts instead of objects
|
241
|
+
############################################################################
|
242
|
+
|
243
|
+
def ext_arg(component, c_args = {})
|
244
|
+
res = component
|
245
|
+
c_args.each do |k, v|
|
246
|
+
res += "[#{k.to_s}=#{v.to_s}]"
|
247
|
+
end
|
248
|
+
res
|
249
|
+
end
|
250
|
+
|
251
|
+
def ext_find(ext_arg_str, scope = nil, index = 0)
|
252
|
+
scope_str = scope.nil? ? '' : ", #{scope}"
|
253
|
+
<<-JS
|
254
|
+
Ext.ComponentQuery.query('#{ext_arg_str}'#{scope_str})[#{index}]
|
255
|
+
JS
|
256
|
+
end
|
257
|
+
|
258
|
+
def ext_var(ext_find_str, var_name='ext_c')
|
259
|
+
<<-JS
|
260
|
+
var #{var_name} = #{ext_find_str};
|
261
|
+
JS
|
262
|
+
end
|
263
|
+
|
264
|
+
def ext_netzkecombo field
|
265
|
+
<<-JS
|
266
|
+
#{ext_find(ext_arg('netzkeremotecombo', name: field))}
|
267
|
+
JS
|
268
|
+
end
|
269
|
+
|
270
|
+
def ext_combo combo_label, c_name='combo'
|
271
|
+
<<-JS
|
272
|
+
#{ext_var(ext_find(ext_arg('combobox', fieldLabel: combo_label)), c_name)}
|
273
|
+
#{c_name} = #{c_name} ||
|
274
|
+
#{ext_find(ext_arg('combobox', name: combo_label))};
|
275
|
+
JS
|
276
|
+
end
|
277
|
+
|
278
|
+
def ext_celleditor(grid_name='grid')
|
279
|
+
<<-JS
|
280
|
+
#{grid_name}.getPlugin('celleditor')
|
281
|
+
JS
|
282
|
+
end
|
283
|
+
|
284
|
+
def ext_row(row, grid_name='grid')
|
285
|
+
<<-JS
|
286
|
+
#{grid_name}.getStore().getAt(#{row})
|
287
|
+
JS
|
288
|
+
end
|
289
|
+
|
290
|
+
def ext_col(col, grid_name='grid')
|
291
|
+
<<-JS
|
292
|
+
#{ext_find(ext_arg('gridcolumn', name: "\"#{col}\""), grid_name)}
|
293
|
+
JS
|
294
|
+
end
|
295
|
+
|
296
|
+
def ext_cell_val(row, col, grid, var_str = 'value')
|
297
|
+
<<-JS
|
298
|
+
#{ext_var(grid, 'grid')}
|
299
|
+
#{ext_var(ext_col(col, 'grid'), 'col')}
|
300
|
+
#{ext_var(ext_row(row, 'grid'), 'row')}
|
301
|
+
var #{var_str} = col.assoc ?
|
302
|
+
row.get('association_values')['#{col}'] :
|
303
|
+
row.get('#{col}');
|
304
|
+
JS
|
305
|
+
end
|
306
|
+
end end end
|