dossier 2.12.2 → 2.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -6
- data/Rakefile +12 -5
- data/VERSION +1 -0
- data/app/controllers/dossier/reports_controller.rb +1 -1
- data/app/views/dossier/reports/show.html.haml +1 -1
- data/lib/dossier.rb +1 -1
- data/lib/dossier/client.rb +4 -1
- data/lib/dossier/configuration.rb +10 -5
- data/lib/dossier/{naming.rb → model.rb} +10 -2
- data/lib/dossier/multi_report.rb +1 -1
- data/lib/dossier/query.rb +1 -1
- data/lib/dossier/report.rb +13 -10
- data/lib/dossier/responder.rb +2 -0
- data/lib/dossier/result.rb +29 -12
- data/lib/dossier/version.rb +1 -1
- data/lib/generators/dossier/views/templates/show.html.haml +1 -1
- data/spec/dossier/adapter/active_record_spec.rb +4 -4
- data/spec/dossier/client_spec.rb +19 -19
- data/spec/dossier/configuration_spec.rb +29 -9
- data/spec/dossier/{naming_spec.rb → model_spec.rb} +6 -1
- data/spec/dossier/query_spec.rb +12 -12
- data/spec/dossier/report_spec.rb +7 -7
- data/spec/dossier/responder_spec.rb +5 -5
- data/spec/dossier/result_spec.rb +5 -5
- data/spec/dossier/stream_csv_spec.rb +5 -3
- data/spec/dossier/version_spec.rb +8 -0
- data/spec/dossier_spec.rb +4 -4
- data/spec/dummy/Rakefile +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/reports/cats/are/super_fun_report.rb +13 -1
- data/spec/dummy/app/reports/employee_report.rb +14 -1
- data/spec/dummy/app/reports/employee_with_custom_view_report.rb +5 -1
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +30 -0
- data/spec/dummy/config/database.yml +6 -5
- data/spec/dummy/db/{test.sqlite3 → dossier_test.sqlite3} +0 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +21983 -0
- data/spec/features/employee_spec.rb +13 -4
- data/spec/fixtures/db/postgresql.yml +4 -0
- data/spec/fixtures/db/postgresql.yml.example +5 -0
- data/spec/fixtures/db/postgresql.yml.travis +4 -0
- data/spec/fixtures/db/sqlite3.yml +1 -1
- data/spec/fixtures/reports/employee.csv +3 -3
- data/spec/fixtures/reports/employee.xls +3 -3
- data/spec/generators/dossier/views/views_spec.rb +17 -10
- data/spec/routing/dossier_routes_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -10
- data/spec/support/factory.rb +38 -0
- metadata +57 -44
- data/spec/dummy/config/database.yml.example +0 -13
- data/spec/dummy/config/database.yml.travis +0 -5
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Dossier::Configuration do
|
4
4
|
|
5
|
-
let(:connection_options){ YAML.
|
5
|
+
let(:connection_options){ YAML.load(ERB.new(File.read Rails.root.join('config', 'dossier.yml')).result)[Rails.env].symbolize_keys }
|
6
6
|
let(:old_database_url) { ENV.delete "DATABASE_URL"}
|
7
7
|
|
8
8
|
before :each do
|
@@ -10,13 +10,17 @@ describe Dossier::Configuration do
|
|
10
10
|
@config = Dossier.configuration
|
11
11
|
end
|
12
12
|
|
13
|
+
after :each do
|
14
|
+
ENV.delete "DATABASE_URL" if ENV.has_key? "DATABASE_URL"
|
15
|
+
end
|
16
|
+
|
13
17
|
after :each do
|
14
18
|
ENV["DATABASE_URL"] = old_database_url
|
15
19
|
end
|
16
20
|
|
17
21
|
describe "defaults" do
|
18
22
|
it "uses the rails configuration directory for the config path" do
|
19
|
-
@config.config_path.
|
23
|
+
expect(@config.config_path).to eq(Rails.root.join("config", "dossier.yml"))
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
@@ -30,20 +34,36 @@ describe Dossier::Configuration do
|
|
30
34
|
end
|
31
35
|
|
32
36
|
it "uses config/dossier.yml to setup the client" do
|
33
|
-
ENV.delete "DATABASE_URL" if ENV.has_key? "DATABASE_URL"
|
34
37
|
expect(Dossier::Client).to receive(:new).with(connection_options)
|
35
38
|
Dossier.configure
|
36
39
|
end
|
37
40
|
|
38
|
-
|
39
|
-
config_path
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
describe "missing a dossier.yml" do
|
42
|
+
let(:config_path) { Rails.root.join('config') }
|
43
|
+
|
44
|
+
before :each do
|
45
|
+
FileUtils.mv config_path.join('dossier.yml'),
|
46
|
+
config_path.join('dossier.yml.test')
|
47
|
+
end
|
48
|
+
|
49
|
+
after :each do
|
50
|
+
FileUtils.mv config_path.join('dossier.yml.test'),
|
51
|
+
config_path.join('dossier.yml')
|
52
|
+
end
|
53
|
+
|
54
|
+
it "will not raise an exception if config/dossier.yml cannot be read and DATABSE_URL is set" do
|
55
|
+
ENV['DATABASE_URL'] = "mysql2://localhost/dossier_test"
|
56
|
+
expect { Dossier.configure }.not_to raise_error
|
57
|
+
end
|
58
|
+
|
59
|
+
it "will raise an error if connection options is blank" do
|
60
|
+
expect { Dossier.configure }.to(
|
61
|
+
raise_error(Dossier::ConfigurationMissingError))
|
62
|
+
end
|
43
63
|
end
|
44
64
|
|
45
65
|
it "will setup the connection options" do
|
46
|
-
@config.connection_options.
|
66
|
+
expect(@config.connection_options).to be_a(Hash)
|
47
67
|
end
|
48
68
|
end
|
49
69
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Dossier::
|
3
|
+
describe Dossier::Model do
|
4
4
|
describe "report naming" do
|
5
5
|
let(:klass) { HelloMyFriendsReport }
|
6
6
|
let(:name) { 'hello_my_friends' }
|
@@ -13,6 +13,11 @@ describe Dossier::Naming do
|
|
13
13
|
expect(described_class.name_to_class(name)).to eq(klass)
|
14
14
|
end
|
15
15
|
|
16
|
+
it "has a to_model" do
|
17
|
+
instance = klass.new
|
18
|
+
expect(instance.to_model).to eq(instance)
|
19
|
+
end
|
20
|
+
|
16
21
|
describe "with namespaces" do
|
17
22
|
let(:klass) { Cats::Are::SuperFunReport }
|
18
23
|
let(:name) { 'cats/are/super_fun' }
|
data/spec/dossier/query_spec.rb
CHANGED
@@ -6,8 +6,8 @@ describe Dossier::Query do
|
|
6
6
|
let(:query) { Dossier::Query.new(report) }
|
7
7
|
|
8
8
|
before :each do
|
9
|
-
report.
|
10
|
-
report.
|
9
|
+
allow(report).to receive(:salary).and_return(2)
|
10
|
+
allow(report).to receive(:ids).and_return([1,2,3])
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "replacing symbols by calling methods of the same name" do
|
@@ -17,10 +17,10 @@ describe Dossier::Query do
|
|
17
17
|
context "when the methods return single values" do
|
18
18
|
|
19
19
|
before :each do
|
20
|
-
report.
|
21
|
-
report.
|
22
|
-
report.
|
23
|
-
report.
|
20
|
+
allow(report).to receive(:sql).and_return("SELECT * FROM employees WHERE id = :id OR girth < :girth OR hired_on = :hired_on")
|
21
|
+
allow(report).to receive(:id).and_return(92)
|
22
|
+
allow(report).to receive(:girth).and_return(3.14)
|
23
|
+
allow(report).to receive(:hired_on).and_return('2013-03-29')
|
24
24
|
end
|
25
25
|
|
26
26
|
it "escapes the values" do
|
@@ -39,8 +39,8 @@ describe Dossier::Query do
|
|
39
39
|
context "when the methods return arrays" do
|
40
40
|
|
41
41
|
before :each do
|
42
|
-
report.
|
43
|
-
report.
|
42
|
+
allow(report).to receive(:sql).and_return("SELECT * FROM employees WHERE stuff IN :stuff")
|
43
|
+
allow(report).to receive(:stuff).and_return([38, 'blue', 'mandible', 2])
|
44
44
|
end
|
45
45
|
|
46
46
|
it "escapes each value in the array" do
|
@@ -60,14 +60,14 @@ describe Dossier::Query do
|
|
60
60
|
context "when it's another string that includes :" do
|
61
61
|
|
62
62
|
it "does not escape a namespaced constant" do
|
63
|
-
report.
|
64
|
-
query.
|
63
|
+
allow(report).to receive(:sql).and_return("SELECT * FROM employees WHERE type = 'Foo::Bar'")
|
64
|
+
expect(query).not_to receive(:Bar)
|
65
65
|
query.to_s
|
66
66
|
end
|
67
67
|
|
68
68
|
it "does not escape a top-level constant" do
|
69
|
-
report.
|
70
|
-
query.
|
69
|
+
allow(report).to receive(:sql).and_return("SELECT * FROM employees WHERE type = '::Foo'")
|
70
|
+
expect(query).not_to receive(:Foo)
|
71
71
|
query.to_s
|
72
72
|
end
|
73
73
|
|
data/spec/dossier/report_spec.rb
CHANGED
@@ -5,7 +5,7 @@ describe Dossier::Report do
|
|
5
5
|
let(:report) { TestReport.new(:foo => 'bar') }
|
6
6
|
|
7
7
|
it "has a report name" do
|
8
|
-
TestReport.report_name.
|
8
|
+
expect(TestReport.report_name).to eq('test')
|
9
9
|
end
|
10
10
|
|
11
11
|
it "has a template name that is the report name" do
|
@@ -29,15 +29,15 @@ describe Dossier::Report do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
it "takes options when initializing" do
|
32
|
-
report.options.
|
32
|
+
expect(report.options).to eq('foo' => 'bar')
|
33
33
|
end
|
34
34
|
|
35
35
|
it 'generates column headers' do
|
36
|
-
report.format_header('Foo').
|
36
|
+
expect(report.format_header('Foo')).to eq 'Foo'
|
37
37
|
end
|
38
38
|
|
39
39
|
it 'allows for column header customization' do
|
40
|
-
report_with_custom_header.format_header(:generic).
|
40
|
+
expect(report_with_custom_header.format_header(:generic)).to eq 'customized'
|
41
41
|
end
|
42
42
|
|
43
43
|
it "has a formatted title" do
|
@@ -62,8 +62,8 @@ describe Dossier::Report do
|
|
62
62
|
end
|
63
63
|
|
64
64
|
it "has callbacks for execute" do
|
65
|
-
Dossier.client.
|
66
|
-
report.
|
65
|
+
allow(Dossier.client).to receive(:execute).and_return([])
|
66
|
+
allow(report).to receive(:before_test_for_build_query)
|
67
67
|
expect(report).to receive(:after_test_for_execute)
|
68
68
|
report.run
|
69
69
|
end
|
@@ -86,7 +86,7 @@ describe Dossier::Report do
|
|
86
86
|
it "will cache the results of the run in `results`" do
|
87
87
|
report = EmployeeReport.new
|
88
88
|
report.run
|
89
|
-
report.results.
|
89
|
+
expect(report.results).not_to be_nil
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -2,18 +2,18 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Dossier::Responder do
|
4
4
|
|
5
|
-
def
|
5
|
+
def mock_out_report_results(report)
|
6
6
|
report.tap { |r|
|
7
|
-
r.
|
8
|
-
r.
|
7
|
+
allow(r).to receive(:results).and_return(results)
|
8
|
+
allow(r).to receive(:raw_results).and_return(results)
|
9
9
|
}
|
10
10
|
end
|
11
11
|
|
12
12
|
let(:results) { double(arrays: [%w[hi], %w[there]], hashes: [{hi: 'there'}]) }
|
13
13
|
let(:report) { EmployeeReport.new }
|
14
|
-
let(:reports) { [
|
14
|
+
let(:reports) { [mock_out_report_results(report)] }
|
15
15
|
let(:controller) {
|
16
|
-
ActionController::Base.new.tap { |controller| controller.
|
16
|
+
ActionController::Base.new.tap { |controller| allow(controller).to receive(:headers).and_return({}) }
|
17
17
|
}
|
18
18
|
let(:responder) { described_class.new(controller, reports, {}) }
|
19
19
|
|
data/spec/dossier/result_spec.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Dossier::Result do
|
4
4
|
|
5
|
-
module
|
5
|
+
module AbstractMock
|
6
6
|
def each
|
7
7
|
adapter_results.rows.each do |row|
|
8
8
|
yield row
|
@@ -17,7 +17,7 @@ describe Dossier::Result do
|
|
17
17
|
let(:report) { TestReport.new }
|
18
18
|
let(:result_row) { {'mascot' => 'platapus', 'cheese' => 'bleu'} }
|
19
19
|
let(:adapter_result) { double(:adapter_result, rows: [result_row.values], headers: result_row.keys) }
|
20
|
-
let(:result_class) { Class.new(described_class) { include
|
20
|
+
let(:result_class) { Class.new(described_class) { include AbstractMock } }
|
21
21
|
let(:result) { result_class.new(adapter_result, report) }
|
22
22
|
|
23
23
|
it "requires each to be overridden" do
|
@@ -51,7 +51,7 @@ describe Dossier::Result do
|
|
51
51
|
end
|
52
52
|
|
53
53
|
it "can return an array of arrays" do
|
54
|
-
result.
|
54
|
+
allow(result).to receive(:headers).and_return(%w[mascot cheese])
|
55
55
|
expect(result.arrays).to eq([%w[mascot cheese], %w[platapus bleu]])
|
56
56
|
end
|
57
57
|
|
@@ -122,7 +122,7 @@ describe Dossier::Result do
|
|
122
122
|
let(:adapter_result_rows) { 7.times.map { result_row.values } }
|
123
123
|
|
124
124
|
before :each do
|
125
|
-
adapter_result.
|
125
|
+
allow(adapter_result).to receive(:rows).and_return(adapter_result_rows)
|
126
126
|
end
|
127
127
|
|
128
128
|
it "has 4 result rows" do
|
@@ -157,7 +157,7 @@ describe Dossier::Result do
|
|
157
157
|
end
|
158
158
|
|
159
159
|
it "does not format the results" do
|
160
|
-
result.
|
160
|
+
expect(result).not_to receive(:format)
|
161
161
|
result.each { |result| }
|
162
162
|
end
|
163
163
|
|
@@ -57,16 +57,18 @@ describe Dossier::StreamCSV do
|
|
57
57
|
describe "exceptions" do
|
58
58
|
let(:output) { String.new }
|
59
59
|
let(:error) { "Woooooooo cats are fluffy!" }
|
60
|
-
before(:each) {
|
60
|
+
before(:each) {
|
61
|
+
allow(collection[0]).to receive(:to_csv).and_raise(error)
|
62
|
+
}
|
61
63
|
|
62
64
|
it "provides a backtrace if local request" do
|
63
|
-
Rails.application.config.
|
65
|
+
allow(Rails.application.config).to receive(:consider_all_requests_local).and_return(true)
|
64
66
|
streamer.each { |line| output << line }
|
65
67
|
expect(output).to include(error)
|
66
68
|
end
|
67
69
|
|
68
70
|
it "provides a simple error if not a local request" do
|
69
|
-
Rails.application.config.
|
71
|
+
allow(Rails.application.config).to receive(:consider_all_requests_local).and_return(false)
|
70
72
|
streamer.each { |line| output << line }
|
71
73
|
expect(output).to match(/something went wrong/)
|
72
74
|
end
|
data/spec/dossier_spec.rb
CHANGED
@@ -2,17 +2,17 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Dossier do
|
4
4
|
it "is a module" do
|
5
|
-
Dossier.
|
5
|
+
expect(Dossier).to be_a(Module)
|
6
6
|
end
|
7
7
|
|
8
8
|
it "is configurable" do
|
9
9
|
Dossier.configure
|
10
|
-
Dossier.configuration.
|
10
|
+
expect(Dossier.configuration).to_not be_nil
|
11
11
|
end
|
12
12
|
|
13
13
|
it "has a configuration" do
|
14
14
|
Dossier.configure
|
15
|
-
Dossier.configuration.
|
15
|
+
expect(Dossier.configuration).to be_a(Dossier::Configuration)
|
16
16
|
end
|
17
17
|
|
18
18
|
it "allows configuration via a block" do
|
@@ -20,7 +20,7 @@ describe Dossier do
|
|
20
20
|
Dossier.configure do |config|
|
21
21
|
config.client = some_client
|
22
22
|
end
|
23
|
-
Dossier.configuration.client.
|
23
|
+
expect(Dossier.configuration.client).to eq(some_client)
|
24
24
|
end
|
25
25
|
|
26
26
|
it "exposes the configurations client via Dossier.client" do
|
data/spec/dummy/Rakefile
ADDED
@@ -2,7 +2,19 @@ module Cats
|
|
2
2
|
module Are
|
3
3
|
class SuperFunReport < Dossier::Report
|
4
4
|
def sql
|
5
|
-
"select '
|
5
|
+
"select #{selections.join(', ')}"
|
6
|
+
end
|
7
|
+
|
8
|
+
def selections
|
9
|
+
columns = %w(cats are super fun)
|
10
|
+
selections = columns.map { |x| "'#{x}' as #{x}" }
|
11
|
+
if ENV['DOSSIER_DB'].to_s === 'postgresql'
|
12
|
+
selections.map! { |x|
|
13
|
+
parts = x.split(' as ')
|
14
|
+
"'#{parts[0][1..-2]}'::character(7) as #{parts[1]}"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
selections
|
6
18
|
end
|
7
19
|
end
|
8
20
|
end
|
@@ -55,12 +55,17 @@ class EmployeeReport < Dossier::Report
|
|
55
55
|
@names ||= options.fetch(:names) { [] }.dup
|
56
56
|
end
|
57
57
|
|
58
|
+
def display_column?(name)
|
59
|
+
name != 'id'
|
60
|
+
end
|
61
|
+
|
58
62
|
def format_salary(amount, row)
|
59
63
|
return "Who's Asking?" if row[:division] == "Corporate Malfeasance"
|
60
64
|
formatter.number_to_currency(amount)
|
61
65
|
end
|
62
66
|
|
63
67
|
def format_hired_on(date)
|
68
|
+
date = Date.parse(date) if String === date
|
64
69
|
date.to_s(:db)
|
65
70
|
end
|
66
71
|
|
@@ -69,11 +74,19 @@ class EmployeeReport < Dossier::Report
|
|
69
74
|
end
|
70
75
|
|
71
76
|
def format_suspended(value)
|
72
|
-
value.
|
77
|
+
value.to_s.in?(%w(1 t)) ? 'Yes' : 'No'
|
73
78
|
end
|
74
79
|
|
75
80
|
def example_before_hook
|
76
81
|
# do some stuff
|
77
82
|
end
|
78
83
|
|
84
|
+
def raw_results
|
85
|
+
super
|
86
|
+
results = query_results.rows.map { |qr|
|
87
|
+
qr.tap { |q| q[4] = format_suspended(q[4]) }
|
88
|
+
}
|
89
|
+
@raw_results ||= Result::Unformatted.new(results, self)
|
90
|
+
end
|
91
|
+
|
79
92
|
end
|
@@ -6,7 +6,7 @@ class EmployeeWithCustomViewReport < Dossier::Report
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def sql
|
9
|
-
"SELECT * FROM employees WHERE suspended =
|
9
|
+
"SELECT * FROM employees WHERE suspended = :suspended"
|
10
10
|
end
|
11
11
|
|
12
12
|
def dragon_color
|
@@ -17,6 +17,10 @@ class EmployeeWithCustomViewReport < Dossier::Report
|
|
17
17
|
@formatter ||= CustomFormatter
|
18
18
|
end
|
19
19
|
|
20
|
+
def suspended
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
20
24
|
module CustomFormatter
|
21
25
|
extend Dossier::Formatter
|
22
26
|
def margery_butts(word)
|
data/spec/dummy/bin/rake
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../../../Gemfile', __FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
|
6
|
+
Bundler.setup
|
7
|
+
|
8
|
+
require "rails/all"
|
9
|
+
|
10
|
+
Bundler.require
|
11
|
+
|
12
|
+
module Dummy
|
13
|
+
class Application < ::Rails::Application
|
14
|
+
config.cache_classes = true
|
15
|
+
config.active_support.deprecation = :stderr
|
16
|
+
config.eager_load = false
|
17
|
+
config.action_controller.allow_forgery_protection = false
|
18
|
+
|
19
|
+
# Raise exceptions instead of rendering exception templates
|
20
|
+
config.action_dispatch.show_exceptions = false
|
21
|
+
# because this belongs here for some reason...??? also in spec_helper
|
22
|
+
# thanks rails 5 :/
|
23
|
+
config.active_support.test_order = :random
|
24
|
+
|
25
|
+
config.secret_token = config.secret_key_base =
|
26
|
+
'http://s3-ec.buzzfed.com/static/enhanced/webdr03/2013/5/25/8/anigif_enhanced-buzz-11857-1369483324-0.gif'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Dummy::Application.initialize!
|