marty 6.1.0 → 8.0.0
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 +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +17 -3
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +3 -2
- data/Gemfile +2 -1
- data/app/assets/javascripts/marty/extjs/extensions/marty.js +23 -1
- data/app/assets/stylesheets/marty/application.css +4 -1
- data/app/assets/stylesheets/marty/dark_mode.css +17 -0
- data/app/components/marty/auth_app.rb +10 -1
- data/app/components/marty/auth_app/client/auth_app.js +4 -0
- data/app/components/marty/extras/layout.rb +2 -2
- data/app/components/marty/extras/misc.rb +1 -1
- data/app/components/marty/log_view.rb +0 -1
- data/app/components/marty/main_auth_app.rb +9 -12
- data/app/components/marty/promise_view.rb +5 -0
- data/app/components/marty/promise_view/client/promise_view.js +11 -0
- data/app/components/marty/schedule_jobs_dashboard.rb +30 -96
- data/app/components/marty/schedule_jobs_grid.rb +118 -0
- data/app/controllers/marty/application_controller.rb +6 -2
- data/app/models/marty/base.rb +48 -48
- data/app/models/marty/config.rb +14 -2
- data/app/models/marty/user.rb +12 -0
- data/app/services/marty/background_job/fetch_missing_in_schedule_cron_jobs.rb +19 -0
- data/app/services/marty/enums/report.rb +18 -0
- data/app/services/marty/promises/delorean/create.rb +16 -27
- data/app/services/marty/promises/ruby/create.rb +10 -1
- data/app/views/layouts/marty/application.html.erb +4 -1
- data/app/views/marty/diagnostic/op.html.erb +3 -1
- data/config/locales/en.yml +2 -0
- data/db/migrate/512_add_promise_priority.rb +9 -0
- data/db/migrate/513_add_priority_to_promise_view.rb +44 -0
- data/db/migrate/514_remove_marty_events.rb +13 -0
- data/delorean/enum_report.dl +11 -0
- data/delorean/table_report.dl +7 -0
- data/docker-compose.dummy.yml +7 -4
- data/lib/marty.rb +5 -2
- data/lib/marty/api/base.rb +15 -9
- data/lib/marty/cache_adapters.rb +2 -0
- data/lib/marty/cache_adapters/mcfly_ruby_cache.rb +1 -5
- data/lib/marty/cache_adapters/memory_and_redis.rb +93 -0
- data/lib/marty/cache_adapters/redis.rb +63 -0
- data/lib/marty/delayed_job/scheduled_job_plugin.rb +33 -0
- data/lib/marty/diagnostic/database.rb +1 -2
- data/lib/marty/logger.rb +50 -17
- data/lib/marty/monkey.rb +26 -6
- data/lib/marty/promise_ruby_job.rb +2 -0
- data/lib/marty/rails_app.rb +29 -0
- data/lib/marty/railtie.rb +1 -0
- data/lib/marty/version.rb +1 -1
- data/marty.gemspec +1 -0
- data/spec/controllers/job_controller_spec.rb +2 -2
- data/spec/dummy/app/components/gemini/cm_auth_app.rb +12 -0
- data/spec/dummy/app/components/gemini/simple_view.rb +17 -0
- data/spec/dummy/app/jobs/test_failing_job.rb +14 -0
- data/spec/dummy/app/models/gemini/helper.rb +90 -1
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/db/migrate/20191101132729_add_activity_flag_to_simple.rb +10 -0
- data/spec/dummy/delorean/blame_report.dl +1 -0
- data/spec/dummy/delorean/enum_report.dl +1 -0
- data/spec/dummy/delorean/marty_fields.dl +1 -0
- data/spec/dummy/delorean/table_report.dl +1 -0
- data/spec/features/data_blame_report_spec.rb +66 -0
- data/spec/features/data_grid_spec.rb +1 -1
- data/spec/features/enum_values_report_spec.rb +76 -0
- data/spec/features/inline_editing_spec.rb +33 -0
- data/spec/features/rule_spec.rb +1 -1
- data/spec/features/schedule_jobs_dashboard_spec.rb +1 -1
- data/spec/features/scripting_spec.rb +1 -1
- data/spec/features/user_list_report_spec.rb +74 -0
- data/spec/fixtures/misc/struct_compare_tests.txt +15 -5
- data/spec/job_helper.rb +39 -0
- data/spec/jobs/cron_job_spec.rb +91 -0
- data/spec/lib/mcfly_model_spec.rb +9 -0
- data/spec/models/promise_spec.rb +168 -1
- data/spec/other/diagnostic/delayed_job_workers_spec.rb +1 -1
- data/spec/performance/caching_spec.rb +99 -0
- data/spec/services/background_job/fetch_missing_in_schedule_cron_jobs_spec.rb +34 -0
- data/spec/support/delayed_job_helpers.rb +3 -3
- data/spec/support/shared_connection.rb +9 -1
- data/spec/support/structure_compare.rb +19 -3
- metadata +39 -6
- data/app/components/marty/event_view.rb +0 -129
- data/app/models/marty/event.rb +0 -317
- data/spec/dummy/db/migrate/20160923183516_add_bulk_pricing_event_ops.rb +0 -8
- data/spec/dummy/delorean/blame_report.dl +0 -268
- data/spec/dummy/delorean/marty_fields.dl +0 -63
- data/spec/dummy/delorean/table_report.dl +0 -34
- data/spec/models/event_spec.rb +0 -272
data/lib/marty/monkey.rb
CHANGED
@@ -70,14 +70,10 @@ class Delorean::BaseModule::NodeCall
|
|
70
70
|
end
|
71
71
|
|
72
72
|
class Delorean::Engine
|
73
|
-
|
73
|
+
# FIXME: Marty::Event no longer exists, we should remove event at some point
|
74
|
+
def background_eval(node, params, attrs, _event = {})
|
74
75
|
raise 'background_eval bad params' unless params.is_a?(Hash)
|
75
76
|
|
76
|
-
unless event.empty?
|
77
|
-
params['p_event'] = event.each_with_object({}) do |(k, v), h|
|
78
|
-
h[k.to_s] = v
|
79
|
-
end
|
80
|
-
end
|
81
77
|
nc = Delorean::BaseModule::NodeCall.new({}, self, node, params)
|
82
78
|
# start the background promise
|
83
79
|
nc | attrs
|
@@ -112,6 +108,25 @@ class Netzke::Base
|
|
112
108
|
end
|
113
109
|
end
|
114
110
|
|
111
|
+
# FIXME: Move to Netzke
|
112
|
+
module Netzke
|
113
|
+
module Basepack
|
114
|
+
class ColumnConfig < AttrConfig
|
115
|
+
alias old_set_defaults set_defaults
|
116
|
+
|
117
|
+
def set_defaults
|
118
|
+
old_set_defaults
|
119
|
+
|
120
|
+
return unless xtype == :checkcolumn
|
121
|
+
# Use default value only if there is a boolean attribute with that name
|
122
|
+
return unless @model_adapter.attr_type(name) == :boolean
|
123
|
+
return if key?(:default_value)
|
124
|
+
|
125
|
+
self.default_value = false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
115
130
|
######################################################################
|
116
131
|
|
117
132
|
# The following is a hack to get around ActiveRecord's broken handling
|
@@ -356,3 +371,8 @@ module Netzke
|
|
356
371
|
end
|
357
372
|
end
|
358
373
|
end
|
374
|
+
|
375
|
+
require 'delayed_cron_job'
|
376
|
+
require_relative './delayed_job/scheduled_job_plugin.rb'
|
377
|
+
|
378
|
+
Delayed::Worker.plugins << Marty::DelayedJob::ScheduledJobPlugin
|
@@ -23,6 +23,7 @@ class Marty::PromiseRubyJob < Struct.new(:promise,
|
|
23
23
|
begin
|
24
24
|
Mcfly.whodunnit = promise.user
|
25
25
|
|
26
|
+
ENV['__promise_id'] = promise.id.to_s
|
26
27
|
mod = module_name.constantize
|
27
28
|
res = { 'result' => mod.send(method_name, *method_args) }
|
28
29
|
rescue StandardError => e
|
@@ -31,6 +32,7 @@ class Marty::PromiseRubyJob < Struct.new(:promise,
|
|
31
32
|
|
32
33
|
promise.set_result(res)
|
33
34
|
process_hook(res)
|
35
|
+
ENV.delete('__promise_id')
|
34
36
|
end
|
35
37
|
|
36
38
|
def process_hook(res)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Marty
|
2
|
+
module RailsApp
|
3
|
+
class << self
|
4
|
+
def application_name
|
5
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.0.0')
|
6
|
+
Rails.application.class.module_parent_name
|
7
|
+
else
|
8
|
+
Rails.application.class.parent_name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def needs_migration?
|
13
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('5.2.0')
|
14
|
+
ActiveRecord::Base.connection.migration_context.needs_migration?
|
15
|
+
else
|
16
|
+
ActiveRecord::Migrator.needs_migration?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parameter_filter_class
|
21
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new('6.0.0')
|
22
|
+
ActiveSupport::ParameterFilter
|
23
|
+
else
|
24
|
+
ActionDispatch::Http::ParameterFilter
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/marty/railtie.rb
CHANGED
data/lib/marty/version.rb
CHANGED
data/marty.gemspec
CHANGED
@@ -208,13 +208,13 @@ describe Marty::JobController, slow: true do
|
|
208
208
|
|
209
209
|
expect_csv = "a,b\r\n1,1\r\n2,4\r\n3,9\r\n"
|
210
210
|
expect(response.body).to eq expect_csv
|
211
|
-
expect(response.
|
211
|
+
expect(response.media_type).to eq 'text/csv'
|
212
212
|
|
213
213
|
get 'download', params: {
|
214
214
|
job_id: promise.parent_id,
|
215
215
|
}
|
216
216
|
|
217
|
-
expect(response.
|
217
|
+
expect(response.media_type).to eq 'application/zip'
|
218
218
|
|
219
219
|
Zip::InputStream.open(StringIO.new(response.body)) do |io|
|
220
220
|
count = 0
|
@@ -13,6 +13,7 @@ class Gemini::CmAuthApp < Marty::MainAuthApp
|
|
13
13
|
:loan_program_view,
|
14
14
|
:my_rule_view,
|
15
15
|
:xyz_rule_view,
|
16
|
+
:simple_view,
|
16
17
|
],
|
17
18
|
}
|
18
19
|
]
|
@@ -33,13 +34,24 @@ class Gemini::CmAuthApp < Marty::MainAuthApp
|
|
33
34
|
a.handler = :netzke_load_component_by_action
|
34
35
|
end
|
35
36
|
|
37
|
+
action :simple_view do |a|
|
38
|
+
a.text = a.tooltip = 'Gemini Simple'
|
39
|
+
a.handler = :netzke_load_component_by_action
|
40
|
+
end
|
41
|
+
|
36
42
|
component :loan_program_view do |c|
|
37
43
|
c.klass = Gemini::LoanProgramView
|
38
44
|
end
|
45
|
+
|
39
46
|
component :my_rule_view do |c|
|
40
47
|
c.klass = Gemini::MyRuleView
|
41
48
|
end
|
49
|
+
|
42
50
|
component :xyz_rule_view do |c|
|
43
51
|
c.klass = Gemini::XyzRuleView
|
44
52
|
end
|
53
|
+
|
54
|
+
component :simple_view do |c|
|
55
|
+
c.klass = Gemini::SimpleView
|
56
|
+
end
|
45
57
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Gemini::SimpleView < Marty::McflyGridPanel
|
2
|
+
has_marty_permissions create: :dev,
|
3
|
+
read: :dev,
|
4
|
+
update: :dev,
|
5
|
+
delete: :dev
|
6
|
+
|
7
|
+
def configure(c)
|
8
|
+
super
|
9
|
+
c.title = 'Gemini Simple'
|
10
|
+
c.model = Gemini::Simple
|
11
|
+
c.attributes = [
|
12
|
+
:user_id,
|
13
|
+
:some_name,
|
14
|
+
:active,
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
@@ -5,8 +5,33 @@ class Gemini::Helper
|
|
5
5
|
|
6
6
|
# Just for testing
|
7
7
|
delorean_fn :sleep, sig: 1 do
|
8
|
-
|seconds|
|
8
|
+
|seconds, label = ''|
|
9
|
+
|
10
|
+
Marty::Logger.info('sleeping', {label: label}) if label.present?
|
11
|
+
|
9
12
|
Kernel.sleep seconds
|
13
|
+
seconds.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.pr_wait(ids)
|
17
|
+
idh = ids.each_with_object({}) do |id, h|
|
18
|
+
h[id] = false
|
19
|
+
end
|
20
|
+
timeout = 60
|
21
|
+
all_done = false
|
22
|
+
loop do
|
23
|
+
idh.each do |id, v|
|
24
|
+
next if v
|
25
|
+
p = Marty::Promise.uncached { Marty::Promise.find_by(id: id) }
|
26
|
+
idh[id] = p.result if p.status
|
27
|
+
end
|
28
|
+
all_done = idh.values.all? { |v| v }
|
29
|
+
break if all_done || timeout == 0
|
30
|
+
timeout -= 1
|
31
|
+
sleep 1
|
32
|
+
end
|
33
|
+
raise "DID NOT FINISH" unless all_done
|
34
|
+
idh.values.map { |h| h['result'] }
|
10
35
|
end
|
11
36
|
|
12
37
|
# Just for testing
|
@@ -57,4 +82,68 @@ class Gemini::Helper
|
|
57
82
|
end
|
58
83
|
true
|
59
84
|
end
|
85
|
+
|
86
|
+
delorean_fn :get_inds do |cnt|
|
87
|
+
(0..cnt-1).to_a
|
88
|
+
end
|
89
|
+
|
90
|
+
delorean_fn :cached_factorial, cache: true do |number|
|
91
|
+
Kernel.sleep 0.001
|
92
|
+
Math.gamma(number + 1)
|
93
|
+
end
|
94
|
+
|
95
|
+
delorean_fn :priority_tester do |reverse, job_cnt|
|
96
|
+
blockers = 8.times.map do |idx|
|
97
|
+
title = "Blocker #{idx}"
|
98
|
+
Marty::Promises::Ruby::Create.call(
|
99
|
+
module_name: 'Gemini::Helper',
|
100
|
+
method_name: 'sleep',
|
101
|
+
method_args: [5, title],
|
102
|
+
params: {
|
103
|
+
p_title: title,
|
104
|
+
_user_id: 1,
|
105
|
+
p_priority: 0,
|
106
|
+
}.compact
|
107
|
+
).as_json.values.first.first
|
108
|
+
end
|
109
|
+
prioritized = job_cnt.times.map do |idx|
|
110
|
+
pri = reverse ? job_cnt - idx : idx
|
111
|
+
title = "Prioritized #{idx} pri=#{pri}"
|
112
|
+
Marty::Promises::Ruby::Create.call(
|
113
|
+
module_name: 'Gemini::Helper',
|
114
|
+
method_name: 'sleep',
|
115
|
+
method_args: [2, title],
|
116
|
+
params: {
|
117
|
+
p_title: title,
|
118
|
+
_user_id: 1,
|
119
|
+
p_priority: pri
|
120
|
+
}.compact
|
121
|
+
).as_json.values.first.first
|
122
|
+
end
|
123
|
+
pr_wait(blockers) + pr_wait(prioritized)
|
124
|
+
end
|
125
|
+
|
126
|
+
delorean_fn :priority_inh_tester do |title|
|
127
|
+
[Marty::Promises::Ruby::Create.call(
|
128
|
+
module_name: 'Gemini::Helper',
|
129
|
+
method_name: 'sleep',
|
130
|
+
method_args: [0, 'child1'],
|
131
|
+
params: {
|
132
|
+
p_title: title + ' child1',
|
133
|
+
_user_id: 1,
|
134
|
+
_parent_id: ENV['__promise_id']&.to_i
|
135
|
+
}.compact
|
136
|
+
).as_json.values.first.first,
|
137
|
+
Marty::Promises::Ruby::Create.call(
|
138
|
+
module_name: 'Gemini::Helper',
|
139
|
+
method_name: 'sleep',
|
140
|
+
method_args: [0, 'child2'],
|
141
|
+
params: {
|
142
|
+
p_title: title + ' child2',
|
143
|
+
_user_id: 1,
|
144
|
+
p_priority: 10,
|
145
|
+
_parent_id: ENV['__promise_id']&.to_i
|
146
|
+
}.compact
|
147
|
+
).as_json.values.first.first]
|
148
|
+
end
|
60
149
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class AddActivityFlagToSimple < ActiveRecord::Migration[5.2]
|
2
|
+
def up
|
3
|
+
add_column :gemini_simples, :active, :boolean, null: false, default: false
|
4
|
+
change_column_default :gemini_simples, :active, nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def down
|
8
|
+
remove_column :gemini_simples, :active
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
../../../delorean/blame_report.dl
|
@@ -0,0 +1 @@
|
|
1
|
+
../../../delorean/enum_report.dl
|
@@ -0,0 +1 @@
|
|
1
|
+
../../../delorean/marty_fields.dl
|
@@ -0,0 +1 @@
|
|
1
|
+
../../../delorean/table_report.dl
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'Data Blame report', js: true do
|
4
|
+
before(:each) do
|
5
|
+
populate_test_users
|
6
|
+
Marty::Script.load_scripts(nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
def go_to_reporting
|
10
|
+
press('Applications')
|
11
|
+
press('Reports')
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_user(uname)
|
15
|
+
u = Marty::User.find_by_login(uname)
|
16
|
+
begin
|
17
|
+
old_u, Mcfly.whodunnit = Mcfly.whodunnit, u
|
18
|
+
yield(u)
|
19
|
+
ensure
|
20
|
+
Mcfly.whodunnit = old_u
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_node node_name
|
25
|
+
wait_for_ajax
|
26
|
+
# hacky: assumes only 1 combobox without label
|
27
|
+
within(:gridpanel, 'report_select', match: :first) do
|
28
|
+
# hacky, hardcoding netzkecombobox dropdown arrow name
|
29
|
+
arrow = find(:input, 'nodename')['data-componentid'] + '-trigger-picker'
|
30
|
+
find(:xpath, ".//div[@id='#{arrow}']").click
|
31
|
+
find(:xpath, "//li[text()='#{node_name}']").click
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'run_script displays proper data' do
|
36
|
+
log_in_as('admin1')
|
37
|
+
go_to_reporting
|
38
|
+
|
39
|
+
tag_grid = netzke_find('tag_grid')
|
40
|
+
script_grid = netzke_find('script_grid')
|
41
|
+
posting_combo = netzke_find('Posting', 'combobox')
|
42
|
+
script_combo = netzke_find('', 'combobox')
|
43
|
+
|
44
|
+
by 'select tag' do
|
45
|
+
wait_for_ajax
|
46
|
+
tag_grid.select_row(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
and_by 'select TableReport script & UserList node' do
|
50
|
+
find('table', text: 'BlameReport').click
|
51
|
+
select_node('Data Blame Report (xlsx)')
|
52
|
+
end
|
53
|
+
|
54
|
+
and_by 'generate report' do
|
55
|
+
wait_for_ajax
|
56
|
+
press('Generate Report')
|
57
|
+
wait_for_ajax
|
58
|
+
expect(page).to have_content('Generate: Data Blame Report')
|
59
|
+
end
|
60
|
+
|
61
|
+
path = Rails.root.join('spec/tmp/downloads/Data Blame Report.xlsx')
|
62
|
+
file_exists = File.file?(path)
|
63
|
+
|
64
|
+
expect(file_exists).to be true
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'Enum Values List report', js: true do
|
4
|
+
before(:each) do
|
5
|
+
populate_test_users
|
6
|
+
Marty::Script.load_scripts(nil)
|
7
|
+
end
|
8
|
+
|
9
|
+
def go_to_reporting
|
10
|
+
press('Applications')
|
11
|
+
press('Reports')
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_user(uname)
|
15
|
+
u = Marty::User.find_by_login(uname)
|
16
|
+
begin
|
17
|
+
old_u, Mcfly.whodunnit = Mcfly.whodunnit, u
|
18
|
+
yield(u)
|
19
|
+
ensure
|
20
|
+
Mcfly.whodunnit = old_u
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def select_node node_name
|
25
|
+
wait_for_ajax
|
26
|
+
# hacky: assumes only 1 combobox without label
|
27
|
+
within(:gridpanel, 'report_select', match: :first) do
|
28
|
+
# hacky, hardcoding netzkecombobox dropdown arrow name
|
29
|
+
arrow = find(:input, 'nodename')['data-componentid'] + '-trigger-picker'
|
30
|
+
find(:xpath, ".//div[@id='#{arrow}']").click
|
31
|
+
find(:xpath, "//li[text()='#{node_name}']").click
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'run_script displays proper data' do
|
36
|
+
log_in_as('admin1')
|
37
|
+
go_to_reporting
|
38
|
+
|
39
|
+
tag_grid = netzke_find('tag_grid')
|
40
|
+
script_grid = netzke_find('script_grid')
|
41
|
+
posting_combo = netzke_find('Posting', 'combobox')
|
42
|
+
script_combo = netzke_find('', 'combobox')
|
43
|
+
|
44
|
+
by 'select tag' do
|
45
|
+
wait_for_ajax
|
46
|
+
tag_grid.select_row(1)
|
47
|
+
end
|
48
|
+
|
49
|
+
and_by 'select TableReport script & UserList node' do
|
50
|
+
find('table', text: 'EnumReport').click
|
51
|
+
select_node('Enum Values List (csv)')
|
52
|
+
end
|
53
|
+
|
54
|
+
and_by 'generate report' do
|
55
|
+
wait_for_ajax
|
56
|
+
press('Generate Report')
|
57
|
+
wait_for_ajax
|
58
|
+
expect(page).to have_content('Generate: Enum Values List')
|
59
|
+
end
|
60
|
+
|
61
|
+
path = Rails.root.join('spec/tmp/downloads/Enum Values List.csv')
|
62
|
+
csv_content = CSV.parse File.read(path)
|
63
|
+
|
64
|
+
expect(csv_content.size > 1).to be true
|
65
|
+
expect(csv_content.first).to eq ['enum_name', 'value']
|
66
|
+
|
67
|
+
expected_rows = [
|
68
|
+
['marty_promise_types', 'delorean'],
|
69
|
+
['marty_promise_types', 'ruby']
|
70
|
+
]
|
71
|
+
|
72
|
+
promise_types = csv_content.select { |v| v[0] == 'marty_promise_types' }
|
73
|
+
|
74
|
+
expect(promise_types).to eq expected_rows
|
75
|
+
end
|
76
|
+
end
|