resque-reports 0.0.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,3 @@
1
1
  # coding: utf-8
2
2
  require 'resque/reports'
3
+ require_relative '../app/jobs/resque/reports/report_job'
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Resque::Reports::VERSION
9
9
  gem.authors = ['Sergey D.']
10
10
  gem.email = ['sclinede@gmail.com']
11
- gem.description = 'Make your custom reports to CSV with resque by simple DSL'
12
- gem.summary = 'resque-reports 0.0.1'
11
+ gem.description = 'Make your custom reports to CSV in background using Resque with simple DSL'
12
+ gem.summary = 'resque-reports'
13
13
  gem.homepage = 'https://github.com/sclinede/resque-reports'
14
14
  gem.license = "MIT"
15
15
 
@@ -18,10 +18,12 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ['lib']
20
20
 
21
- gem.add_dependency 'resque-integration', '~> 0.2.9'
22
- gem.add_dependency 'facets', '>= 2.9.3'
21
+ gem.add_dependency 'resque-integration', '>= 0.2.9'
22
+ gem.add_dependency 'activesupport' # избавиться от зависимости
23
23
 
24
24
  gem.add_development_dependency "bundler", "~> 1.3"
25
25
  gem.add_development_dependency "rake"
26
- gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'rspec', '>= 2.14.0'
27
+ gem.add_development_dependency 'rspec-given', '~> 3.0'
28
+ gem.add_development_dependency 'simplecov'
27
29
  end
@@ -0,0 +1,196 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+ require 'stringio'
4
+
5
+ require 'resque-reports'
6
+
7
+ class MyTypeReport < Resque::Reports::BaseReport
8
+ extension :type
9
+
10
+ def write(io, force = false)
11
+ write_line io, build_table_header
12
+
13
+ data_each(true) do |element|
14
+ write_line io, build_table_row(element)
15
+ end
16
+ end
17
+
18
+ def write_line(io, row)
19
+ io << "#{row.nil? ? 'nil' : row.join('|')}\r\n"
20
+ end
21
+ end
22
+
23
+ class MyReport < MyTypeReport
24
+ queue :my_type_reports
25
+ source :select_data
26
+ encoding UTF8
27
+
28
+ directory File.join(Dir.tmpdir, 'resque-reports')
29
+
30
+ table do |element|
31
+ column 'First one', :decorate_first
32
+ column 'Second', decorate_second(element[:two])
33
+ end
34
+
35
+ create do |param|
36
+ @main_param = param
37
+ end
38
+
39
+ def decorate_first(element)
40
+ "decorated: #{element[:one]}"
41
+ end
42
+
43
+ def decorate_second(text)
44
+ "#{text} - is second"
45
+ end
46
+
47
+ def select_data
48
+ [{one: 'one', two: 'one'}, {one: @main_param, two: @main_param}]
49
+ end
50
+ end
51
+
52
+ describe 'Resque::Reports::BaseReport successor' do
53
+ let(:io) { StringIO.new }
54
+ let(:my_report) { MyReport.new('test') }
55
+ let(:dummy) { Resque::Reports::Extensions::Dummy.new }
56
+
57
+ describe '#extension' do
58
+ before { File.stub(:exists? => true) }
59
+
60
+ it { File.extname(my_report.filename).should eq '.type' }
61
+ end
62
+
63
+ describe '#source' do
64
+ before { my_report.stub(select_data: [dummy]) }
65
+
66
+ it { my_report.should_receive(:select_data) }
67
+
68
+ after { my_report.build true }
69
+ end
70
+
71
+ describe '#directory' do
72
+ subject { my_report.instance_variable_get(:@cache_file) }
73
+ let(:tmpdir) { File.join(Dir.tmpdir, 'resque-reports') }
74
+
75
+ it { subject.instance_variable_get(:@dir).should eq tmpdir }
76
+ end
77
+
78
+ describe '#create' do
79
+ it { my_report.instance_variable_get(:@main_param).should eq 'test' }
80
+ end
81
+
82
+ describe '#encoding' do
83
+ subject { my_report.instance_variable_get(:@cache_file) }
84
+ let(:utf_coding) { Resque::Reports::Extensions::Encodings::UTF8 }
85
+
86
+ it { subject.instance_variable_get(:@coding).should eq utf_coding }
87
+ end
88
+
89
+ describe '#write' do
90
+ subject { MyReport.new('#write test') }
91
+
92
+ before do
93
+ subject.stub(:data_each) { |&block| block.call(dummy) }
94
+ subject.stub(build_table_row: ['row'])
95
+ subject.stub(build_table_header: ['header'])
96
+
97
+ subject.write(io)
98
+ end
99
+
100
+ it { io.string.should eq "header\r\nrow\r\n" }
101
+ end
102
+
103
+ describe '#bg_build' do
104
+ let(:job_class) { Resque::Reports::ReportJob }
105
+
106
+ context 'when report is building twice' do
107
+ subject { MyReport.new('#bg_build test') }
108
+
109
+ before { job_class.stub(enqueue: 'job_id') }
110
+
111
+ it do
112
+ job_class
113
+ .should_receive(:enqueue)
114
+ .with('MyReport', '["#bg_build test",true]')
115
+ .twice
116
+ end
117
+
118
+ after do
119
+ 2.times { subject.bg_build true }
120
+ end
121
+ end
122
+
123
+ context 'when report is building' do
124
+ subject { MyReport.new('#bg_build test') }
125
+
126
+ before { job_class.stub(enqueue: 'job_id') }
127
+
128
+ it do
129
+ job_class
130
+ .should_receive(:enqueue)
131
+ .with('MyReport', '["#bg_build test",true]')
132
+ end
133
+
134
+ after { subject.bg_build true }
135
+ end
136
+
137
+ context 'when report is not build yet' do
138
+ subject { MyReport.new('#bg_build test') }
139
+
140
+ before do
141
+ job_class.stub(enqueued?: double('Meta', meta_id: 'enqueued_job_id'))
142
+ end
143
+
144
+ it { subject.bg_build(true).should eq 'enqueued_job_id' }
145
+ end
146
+ end
147
+
148
+ describe '#build' do
149
+ subject { MyReport.new('#build test') }
150
+
151
+ it { subject.should_receive(:decorate_first).twice }
152
+
153
+ after { subject.build true }
154
+
155
+ context 'when report was built' do
156
+ subject { MyReport.new('was built test') }
157
+
158
+ before { subject.build true }
159
+
160
+ its(:exists?) { should be_true }
161
+ it do
162
+ File.read(subject.filename)
163
+ .should eq <<-REPORT.gsub(/^ {12}/, '')
164
+ First one|Second\r
165
+ decorated: one|one - is second\r
166
+ decorated: was built test|was built test - is second\r
167
+ REPORT
168
+ end
169
+ end
170
+ end
171
+
172
+ describe '#data_each' do
173
+ subject { MyReport.new('#data_each test') }
174
+
175
+ it { subject.should_receive(:data_each) }
176
+
177
+ after { subject.write(io) }
178
+ end
179
+
180
+ describe '#build_table_header' do
181
+ subject { MyReport.new('#build_table_header test') }
182
+
183
+ it { subject.should_receive(:build_table_header) }
184
+
185
+ after { subject.write(io) }
186
+ end
187
+
188
+ describe '#build_table_row' do
189
+ subject { MyReport.new('#build_table_row test') }
190
+
191
+ it { subject.should_receive(:build_table_row).twice }
192
+
193
+ after { subject.write(io) }
194
+ end
195
+
196
+ end
@@ -0,0 +1,89 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+ require 'stringio'
4
+
5
+ require 'resque/reports/csv_report'
6
+
7
+ class MyCsvReport < Resque::Reports::CsvReport
8
+ queue :csv_reports
9
+ source :select_data
10
+ encoding UTF8
11
+
12
+ csv_options col_sep: ',', row_sep: "\n"
13
+
14
+ directory File.join(Dir.home, '.resque-reports')
15
+
16
+ table do |element|
17
+ column 'First one', :decorate_first
18
+ column 'Second', "#{element} - is second"
19
+ end
20
+
21
+ create do |param|
22
+ @main_param = param
23
+ end
24
+
25
+ def decorate_first(element)
26
+ "decorated: #{element}"
27
+ end
28
+
29
+ def select_data
30
+ [:one, @main_param]
31
+ end
32
+ end
33
+
34
+ class MyCsvDefaultsReport < Resque::Reports::CsvReport
35
+ source :select_data
36
+ encoding UTF8
37
+
38
+ directory File.join(Dir.tmpdir, 'resque-reports')
39
+
40
+ table do |element|
41
+ column 'Uno', "#{element} - is value"
42
+ end
43
+
44
+ def select_data
45
+ [:one, @main_param]
46
+ end
47
+ end
48
+
49
+ describe 'Resque::Reports::CsvReport successor' do
50
+ describe '.csv_options' do
51
+ context 'when custom options not set' do
52
+ subject { MyCsvDefaultsReport.new }
53
+
54
+ it 'sets csv_options defaults' do
55
+ subject.options.should eq MyCsvReport::DEFAULT_CSV_OPTIONS
56
+ end
57
+ end
58
+
59
+ context 'when custom options are set' do
60
+ subject { MyCsvReport.new('csv_options test') }
61
+
62
+ let(:my_options) do
63
+ MyCsvReport::DEFAULT_CSV_OPTIONS.merge(col_sep: ',', row_sep: "\n")
64
+ end
65
+
66
+ it 'merges csv_options with defaults' do
67
+ subject.options.should eq my_options
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#build' do
73
+ context 'when report was built' do
74
+ subject { MyCsvReport.new('was built test') }
75
+
76
+ before { subject.build true }
77
+
78
+ its(:exists?) { should be_true }
79
+ it do
80
+ File.read(subject.filename)
81
+ .should eq <<-CSV.gsub(/^ {12}/, "")
82
+ First one,Second
83
+ decorated: one,one - is second
84
+ decorated: was built test,was built test - is second
85
+ CSV
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,140 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+ require 'resque-reports'
4
+
5
+ module Reports
6
+ class MyCsvReport < Resque::Reports::CsvReport
7
+ queue :csv_reports
8
+ source :select_data
9
+ encoding UTF8
10
+
11
+ directory File.join(Dir.home, '.resque-reports')
12
+
13
+ table do |element|
14
+ column 'First', "#{element} - is first"
15
+ end
16
+
17
+ create do |param1, param2|
18
+ @main_param = param1
19
+ @secondary_param = param2
20
+ end
21
+
22
+ def select_data
23
+ [:one, @main_param]
24
+ end
25
+ end
26
+ end
27
+
28
+ describe Resque::Reports::ReportJob do
29
+ let(:my_report) { Reports::MyCsvReport.new('.execute test', 'test') }
30
+ let(:exec_params) do
31
+ ['Reports::MyCsvReport', '[".execute test", "test",true]']
32
+ end
33
+
34
+ describe '.execute' do
35
+ before { Reports::MyCsvReport.stub(new: my_report) }
36
+
37
+ context 'when building report' do
38
+ before { my_report.stub(build: nil) }
39
+
40
+ it do
41
+ expect(Reports::MyCsvReport)
42
+ .to receive(:new).with('.execute test', 'test')
43
+ end
44
+ it { expect(my_report).to receive(:build).with(true) }
45
+
46
+ after { described_class.execute(*exec_params) }
47
+
48
+ end
49
+ context 'when wrong class given' do
50
+ it 'sends invalid class name' do
51
+ expect { described_class.execute('MyWrongReport', '[true]') }
52
+ .to raise_error(NameError)
53
+ end
54
+
55
+ it 'sends class that is not BaseReport successor' do
56
+ expect { described_class.execute('Object', '[true]') }
57
+ .to raise_error(RuntimeError)
58
+ end
59
+ end
60
+
61
+ context 'when events are firing' do
62
+ before do
63
+ described_class.stub(get_meta: {})
64
+ described_class.get_meta.stub(save: true)
65
+ end
66
+
67
+ context 'when progress total is zero' do
68
+ before do
69
+ my_report.stub(select_data: [])
70
+ my_report.stub(data_size: 0)
71
+ end
72
+
73
+ it { described_class.should_not_receive(:at) }
74
+
75
+ after { described_class.execute(*exec_params) }
76
+ end
77
+
78
+ context 'when works default handlers' do
79
+ context 'when error occurs' do
80
+ before { my_report.stub(:build_table_row) { fail 'Custom error' } }
81
+
82
+ it do
83
+ expect { described_class.execute(*exec_params) }
84
+ .to raise_error('Custom error')
85
+ end
86
+ end
87
+
88
+ context 'when progress is changed' do
89
+ it { described_class.should_receive(:at).with(2, 2, nil) }
90
+
91
+ after { described_class.execute(*exec_params) }
92
+ end
93
+ end
94
+
95
+ context 'when works custom handlers' do
96
+ context 'when error occurs' do
97
+ before do
98
+ my_report.stub(:error_message) { |e| fail "Boom! #{e.message}" }
99
+ my_report.stub(:build_table_row) { fail 'Custom error' }
100
+ end
101
+
102
+ it do
103
+ expect { described_class.execute(*exec_params) }
104
+ .to raise_error('Boom! Custom error')
105
+ end
106
+ end
107
+
108
+ context 'when progress is changed' do
109
+ before do
110
+ my_report.stub(:progress_message) do |progress, total|
111
+ "my progress: #{progress} / #{total}"
112
+ end
113
+ end
114
+
115
+ it do
116
+ described_class
117
+ .should_receive(:at)
118
+ .with(2, 2, 'my progress: 2 / 2')
119
+ end
120
+
121
+ after { described_class.execute(*exec_params) }
122
+ end
123
+ end
124
+
125
+ context 'when task is performed by resque' do
126
+ context 'when error occurs' do
127
+ before do
128
+ my_report.stub(:error_message) { |e| fail "Boom! #{e.message}" }
129
+ my_report.stub(:build_table_row) { fail 'Custom error' }
130
+ end
131
+
132
+ it do
133
+ expect { described_class.execute(*exec_params) }
134
+ .to raise_error('Boom! Custom error')
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end