bespoke 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ module Bespoke
4
+ class Export
5
+ describe Filter do
6
+ let(:join) { basic_join }
7
+ let(:db) { join.database }
8
+ let(:table) { db[:two] }
9
+ let(:templates) { {} }
10
+ let(:filter) { Filter.new(table, templates) }
11
+
12
+ describe '#apply' do
13
+ before do
14
+ join.load_csv(:one, fixture('one.csv'))
15
+ join.load_csv(:two, fixture('two.csv'))
16
+ end
17
+
18
+ it 'must not allow SQLi vulnerabilities into clients through the selects templates' do
19
+ templates[:selects] = { foo_id: '* FROM `two` -- ' }
20
+ dataset = filter.apply
21
+ expect(dataset.first).to eq({foo_id: '* FROM `two` -- '})
22
+ end
23
+
24
+ it 'must not allow SQLi vulnerabilities into clients through the joins templates as strings' do
25
+ templates[:joins] = 'foos; SELECT * FROM `one`;'
26
+ dataset = filter.apply
27
+ expect { dataset.all }.to raise_error
28
+ end
29
+
30
+ it 'must not allow SQLi vulnerabilities into clients through the joins templates as a hash' do
31
+ templates[:joins] = { one: ['one_id', 'id` WHERE 1=1; -- `'] }
32
+ dataset = filter.apply
33
+ expect { dataset.all }.to raise_error
34
+ end
35
+
36
+ it 'must not allow SQLi vulnerabilities into clients through the where template' do
37
+ templates[:where] = '1=1); (DROP TABLE `one`'
38
+ dataset = filter.apply
39
+ expect(dataset.first).to eq({description: 'Cryptocurrency', id: 1, one_id: 1})
40
+ expect(db[:one].all).to_not be_empty
41
+ end
42
+ end
43
+
44
+ describe '#sql' do
45
+ it 'must default to selecting all fields from the table' do
46
+ expect(filter.sql).to start_with 'SELECT * FROM'
47
+ end
48
+
49
+ it 'must select from the specified table' do
50
+ expect(filter.sql).to end_with 'FROM `two`'
51
+ end
52
+
53
+ context 'when processing the selects key from the template hash' do
54
+ it 'must select the columns with the ailases specified' do
55
+ templates[:selects] = {course_id: :bar, user_id: :baz}
56
+ expect(filter.sql).to include "`bar` AS 'course_id'"
57
+ expect(filter.sql).to include "`baz` AS 'user_id'"
58
+ end
59
+ end
60
+
61
+ context 'processing the joins key of the template hash' do
62
+ it 'must not include any joins if the key is nil' do
63
+ templates[:joins] = nil
64
+ expect(filter.sql).to_not include 'JOIN'
65
+ end
66
+
67
+ it 'must apply a left join for each element in an array' do
68
+ templates[:joins] = [:two, :three]
69
+ expect(filter.sql).to include 'LEFT JOIN `two`'
70
+ expect(filter.sql).to include 'LEFT JOIN `three`'
71
+ end
72
+
73
+ it 'must apply a hash of joins using the columns supplied' do
74
+ templates[:joins] = {'one' => ['id', 'two_id']}
75
+ expect(filter.sql).to include 'LEFT JOIN `one` ON (`two`.`id` = `one`.`two_id`)'
76
+ end
77
+ end
78
+
79
+ context 'processing the where key of the template hash' do
80
+ it 'must not include a where clause when the key is nil' do
81
+ templates[:where] = nil
82
+ expect(filter.sql).to_not include 'WHERE'
83
+ end
84
+
85
+ it 'must directly apply a string SQL fragment' do
86
+ fragment = '`two`.`id` IS NOT NULL'
87
+ templates[:where] = fragment
88
+ expect(filter.sql).to include fragment
89
+ end
90
+ end
91
+ end
92
+
93
+ describe 'custom functions' do
94
+ it "uses :functions" do
95
+ templates[:selects] = { course_id: "<myfn()>" }
96
+ templates[:functions] = { myfn: ->(args) { "MYFN" } }
97
+ expect(filter.sql).to include "MYFN"
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ module Bespoke
4
+ describe Export do
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe Bespoke::Join do
4
+ let(:join) { basic_join }
5
+
6
+ context "table 'one'" do
7
+ before { join.load_csv('one', fixture('one.csv')) }
8
+
9
+ it "loads csv" do
10
+ join.database[:one].count.should == 2
11
+ end
12
+
13
+ context "and table 'two'" do
14
+ before { join.load_csv('two', fixture('two.csv')) }
15
+
16
+ it "loads csv" do
17
+ join.database[:two].count.should == 3
18
+ end
19
+
20
+ it "joins" do
21
+ sql = "SELECT name, description FROM one LEFT JOIN two ON one.id = two.one_id"
22
+ join.query(sql).map { |row| row }.
23
+ should == [
24
+ {:name=>"bitcoin", :description=>"Cryptocurrency"},
25
+ {:name=>"bitcoin", :description=>"Investment"},
26
+ {:name=>"nxt", :description=>"Alternate Cryptocurrency"}
27
+ ]
28
+ end
29
+ end
30
+ end
31
+
32
+ describe '#database' do
33
+ let(:db) { join.database }
34
+
35
+ describe 'the concat_ws() function added to the database' do
36
+ it 'must join strings using the specified delemeter' do
37
+ result = db.get{ concat_ws('foo', 'bar', 'baz', '-') }
38
+ expect(result).to eq 'foo-bar-baz'
39
+ end
40
+
41
+ it 'must eliminate nulls from the args before joining' do
42
+ result = db.get{ concat_ws('foo', nil, 'baz', '-') }
43
+ expect(result).to eq 'foo-baz'
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#projection_named(table_name)' do
49
+ it 'must return the table with the specified name' do
50
+ table = join.projection_named 'one'
51
+ expect(table.opts[:from]).to eq [:one]
52
+ end
53
+
54
+ it 'must return nil when the projection requested does not exist' do
55
+ table = join.projection_named 'foos'
56
+ expect(table).to be_nil
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ module Bespoke
4
+ describe Projection do
5
+ before do
6
+ @projection = Projection.new(:foo)
7
+ end
8
+
9
+ describe '#initialize' do
10
+ it 'must evaluate the supplied block in the context of the new object' do
11
+ projection = Projection.new(:bar) do
12
+ field :foo, :integer
13
+ field :bar, :string
14
+ field :id, :integer, primary_key: true
15
+ end
16
+
17
+ foo_field = projection.fields.find{|f| f.name == :foo}
18
+ expect(foo_field).to_not be_nil
19
+
20
+ bar_field = projection.fields.find{|f| f.name == :bar}
21
+ expect(bar_field).to_not be_nil
22
+
23
+ id_field = projection.fields.find{|f| f.name == :id}
24
+ expect(id_field).to_not be_nil
25
+ expect(id_field).to be_primary_key
26
+ end
27
+
28
+ it 'must yeild the projdction to the block if its arity is 1' do
29
+ projection = Projection.new(:bar) do |p|
30
+ p.field :foo, :integer
31
+ p.field :bar, :string
32
+ p.field :id, :integer, primary_key: true
33
+ end
34
+
35
+ foo_field = projection.fields.find{|f| f.name == :foo}
36
+ expect(foo_field).to_not be_nil
37
+
38
+ bar_field = projection.fields.find{|f| f.name == :bar}
39
+ expect(bar_field).to_not be_nil
40
+
41
+ id_field = projection.fields.find{|f| f.name == :id}
42
+ expect(id_field).to_not be_nil
43
+ expect(id_field).to be_primary_key
44
+ end
45
+ end
46
+
47
+ describe '#field' do
48
+ it 'must add a new field to the fields array with the supplied attributes' do
49
+ @projection.field(:foo, :integer)
50
+ field = @projection.fields.last
51
+ expect(field.name).to eq :foo
52
+ expect(field.type).to eq :integer
53
+ end
54
+ end
55
+
56
+ describe '#ensure_primary_key!' do
57
+ it 'must raise an exception if there are no fields present' do
58
+ expect { @projection.ensure_primary_key! }.to raise_error Projection::NoPrimaryKeyError
59
+ end
60
+
61
+ context "when there is a ref_id column specified but isn't tagged as the PK" do
62
+ before do
63
+ @original_stderr = $stderr
64
+ $stderr = @new_stderr = StringIO.new
65
+ @projection.field :ref_id, :string
66
+ @projection.field :name, :string
67
+ @projection.field :birth_date, :date
68
+ end
69
+
70
+ after do
71
+ $stderr = @original_stderr
72
+ end
73
+
74
+ it 'must add the primary key specification to the ref_id column' do
75
+ @projection.ensure_primary_key!
76
+ ref_field = @projection.fields.find{|f| f.name == :ref_id }
77
+ expect(ref_field).to be_primary_key
78
+ end
79
+
80
+ it 'must issue a deprecation warning when setting the implied primary key' do
81
+ @projection.ensure_primary_key!
82
+ expect(@new_stderr.string).to_not be_empty
83
+ end
84
+
85
+ it 'must not set the primary key specification if another is already supplied' do
86
+ @projection.field :id, :integer, primary_key: true
87
+ @projection.ensure_primary_key!
88
+ ref_field = @projection.fields.find{|f| f.name == :ref_id }
89
+ expect(ref_field).to_not be_primary_key
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe Bespoke::Template do
4
4
  let(:projection) {
@@ -21,8 +21,8 @@ describe Bespoke::Template do
21
21
 
22
22
  it "last element is the only element where 'last' is true" do
23
23
  first = fields[0..-2].map{ |f| f[:last] }
24
- first.all?{ |x| x == false }.should be_true
25
- fields[-1][:last].should be_true
24
+ expect(first).to be_all {|item| item == false}
25
+ expect(fields[-1][:last]).to eq true
26
26
  end
27
27
 
28
28
  it "has optional function field" do
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ module Bespoke
4
+ describe TypedField do
5
+ describe '#initialize(name, type, options_or_xpath = )' do
6
+ context 'when passed an xpath string as the last argument' do
7
+ before do
8
+ @original_stderr = $stderr
9
+ $stderr = @new_stderr = StringIO.new
10
+ @field = TypedField.new(:foo, :integer, '/foo/bar*')
11
+ end
12
+
13
+ after do
14
+ $stderr = @original_stderr
15
+ end
16
+
17
+ it 'must set the xpath attribute' do
18
+ expect(@field.xpath).to eq '/foo/bar*'
19
+ end
20
+
21
+ it 'must issue a deprecation warning to stderr indicating this is a deprecated use' do
22
+ expect($stderr.string).to_not be_empty
23
+ end
24
+
25
+ it 'must set the column_options to an empty hash' do
26
+ expect(@field.column_options).to eq Hash.new
27
+ end
28
+ end
29
+
30
+ context 'when passed an xpath array as the last argument' do
31
+ before do
32
+ @original_stderr = $stderr
33
+ $stderr = @new_stderr = StringIO.new
34
+ @field = TypedField.new(:foo, :integer, ["sif:EmailList/sif:Email[@Type='Primary']", 'split-get-first'])
35
+ end
36
+
37
+ after do
38
+ $stderr = @original_stderr
39
+ end
40
+
41
+ it 'must set the xpath attribute' do
42
+ expect(@field.xpath).to eq ["sif:EmailList/sif:Email[@Type='Primary']", 'split-get-first']
43
+ end
44
+
45
+ it 'must issue a deprecation warning to stderr indicating this is a deprecated use' do
46
+ expect($stderr.string).to_not be_empty
47
+ end
48
+
49
+ it 'must set the column_options to an empty hash' do
50
+ expect(@field.column_options).to eq Hash.new
51
+ end
52
+ end
53
+
54
+ context ' when passed a hash as the last argument' do
55
+ it 'must extract the xpath from the supplied hash' do
56
+ field = TypedField.new(:foo, :string, xpath: '/foo/bar/baz')
57
+ expect(field.xpath).to eq '/foo/bar/baz'
58
+ end
59
+
60
+ it 'must keep the rest of the options for use by sequel in the column_options attribute' do
61
+ field = TypedField.new(:foo, :string, {
62
+ null: false,
63
+ unique: true,
64
+ xpath: '/bar/qux/flubble',
65
+ })
66
+ expect(field.column_options).to eq({null: false, unique: true})
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#make_primary_key!' do
72
+ before do
73
+ @field = TypedField.new(:foo, :string)
74
+ end
75
+
76
+ specify { expect(@field).to_not be_primary_key }
77
+
78
+ it 'must make the field indicate it is a primary key' do
79
+ @field.make_primary_key!
80
+ expect(@field).to be_primary_key
81
+ end
82
+ end
83
+
84
+ describe '#primary_key?' do
85
+ it 'must return false when not specified by the column options' do
86
+ field = TypedField.new(:foo, :string)
87
+ expect(field).to_not be_primary_key
88
+ end
89
+
90
+ it 'must return false when specified as false in the column options' do
91
+ field = TypedField.new(:foo, :string, primary_key: false)
92
+ expect(field).to_not be_primary_key
93
+ end
94
+
95
+ it 'must return true when specified as true in the column options' do
96
+ field = TypedField.new(:foo, :string, primary_key: true)
97
+ expect(field).to be_primary_key
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,4 +1,4 @@
1
- require_relative 'spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe Bespoke::XSLTProc do
4
4
  let(:xslt) { File.read(fixture('books_to_csv.xslt')) }
@@ -1,8 +1,21 @@
1
- $LOAD_PATH << File.expand_path(File.join(*%w[ .. lib ]), File.dirname(__FILE__))
1
+ require 'pry'
2
+
3
+ if ENV['CHECK_SPEC_COVERAGE']
4
+ require 'simplecov'
5
+ SimpleCov.start do
6
+ add_filter '/spec/'
7
+ end
8
+ end
2
9
 
3
10
  require "bespoke"
4
11
  require "bespoke/dsl"
5
12
 
6
- def fixture(*fileparts)
7
- File.join(File.dirname(__FILE__), 'fixtures', *fileparts)
13
+ Dir.glob(File.expand_path(File.join(*%w{.. support ** *.rb}), __FILE__)).each do |file|
14
+ require file
15
+ end
16
+
17
+ RSpec.configure do |config|
18
+ config.include FixtureHelpers
19
+ config.filter_run_including focus: true
20
+ config.run_all_when_everything_filtered = true
8
21
  end
@@ -0,0 +1,24 @@
1
+ module FixtureHelpers
2
+ def fixture(*fileparts)
3
+ File.join(File.dirname(__FILE__), '..', 'fixtures', *fileparts)
4
+ end
5
+
6
+ def basic_join
7
+ Bespoke::Join.new(basic_projections)
8
+ end
9
+
10
+ def basic_projections
11
+ [
12
+ Bespoke::Projection.new(:one) {
13
+ field :id, :integer, primary_key: true
14
+ field :name, :string
15
+ },
16
+ Bespoke::Projection.new(:two) {
17
+ field :id, :integer, primary_key: true
18
+ field :one_id, :integer
19
+ field :description, :string
20
+ }
21
+ ]
22
+ end
23
+ end
24
+
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bespoke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-04-04 00:00:00.000000000 Z
12
+ date: 2014-08-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -44,7 +44,7 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '2.6'
46
46
  - !ruby/object:Gem::Dependency
47
- name: debugger
47
+ name: guard
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
@@ -60,14 +60,14 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  - !ruby/object:Gem::Dependency
63
- name: rake
63
+ name: guard-rspec
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
67
  - - ! '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
- type: :runtime
70
+ type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
@@ -76,7 +76,7 @@ dependencies:
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  - !ruby/object:Gem::Dependency
79
- name: docile
79
+ name: rake
80
80
  requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
@@ -139,6 +139,22 @@ dependencies:
139
139
  - - ! '>='
140
140
  - !ruby/object:Gem::Version
141
141
  version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: activesupport
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ type: :runtime
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
142
158
  description: Bespoke does in-memory object joins using mustache templates
143
159
  email:
144
160
  - duane@instructure.com
@@ -149,9 +165,17 @@ files:
149
165
  - bespoke.gemspec
150
166
  - readme.md
151
167
  - lib/bespoke/dsl.rb
168
+ - lib/bespoke/export/filter/function_call.rb
169
+ - lib/bespoke/export/filter/identifier.rb
170
+ - lib/bespoke/export/filter/select_template.rb
171
+ - lib/bespoke/export/filter/string_literal.rb
172
+ - lib/bespoke/export/filter.rb
173
+ - lib/bespoke/export.rb
152
174
  - lib/bespoke/join.rb
175
+ - lib/bespoke/mustache.rb
153
176
  - lib/bespoke/projection.rb
154
177
  - lib/bespoke/template.rb
178
+ - lib/bespoke/typed_field.rb
155
179
  - lib/bespoke/version.rb
156
180
  - lib/bespoke/xsltproc.rb
157
181
  - lib/bespoke.rb
@@ -161,10 +185,18 @@ files:
161
185
  - spec/fixtures/sif.student_personal.xslt
162
186
  - spec/fixtures/sif.xslt.mustache
163
187
  - spec/fixtures/two.csv
164
- - spec/join_spec.rb
188
+ - spec/lib/bespoke/export/filter/function_call_spec.rb
189
+ - spec/lib/bespoke/export/filter/identifier_spec.rb
190
+ - spec/lib/bespoke/export/filter/select_template_spec.rb
191
+ - spec/lib/bespoke/export/filter_spec.rb
192
+ - spec/lib/bespoke/export_spec.rb
193
+ - spec/lib/bespoke/join_spec.rb
194
+ - spec/lib/bespoke/projection_spec.rb
195
+ - spec/lib/bespoke/template_spec.rb
196
+ - spec/lib/bespoke/typed_field_spec.rb
197
+ - spec/lib/bespoke/xsltproc_spec.rb
165
198
  - spec/spec_helper.rb
166
- - spec/template_spec.rb
167
- - spec/xsltproc_spec.rb
199
+ - spec/support/fixture_helpers.rb
168
200
  homepage:
169
201
  licenses: []
170
202
  post_install_message:
@@ -196,8 +228,16 @@ test_files:
196
228
  - spec/fixtures/sif.student_personal.xslt
197
229
  - spec/fixtures/sif.xslt.mustache
198
230
  - spec/fixtures/two.csv
199
- - spec/join_spec.rb
231
+ - spec/lib/bespoke/export/filter/function_call_spec.rb
232
+ - spec/lib/bespoke/export/filter/identifier_spec.rb
233
+ - spec/lib/bespoke/export/filter/select_template_spec.rb
234
+ - spec/lib/bespoke/export/filter_spec.rb
235
+ - spec/lib/bespoke/export_spec.rb
236
+ - spec/lib/bespoke/join_spec.rb
237
+ - spec/lib/bespoke/projection_spec.rb
238
+ - spec/lib/bespoke/template_spec.rb
239
+ - spec/lib/bespoke/typed_field_spec.rb
240
+ - spec/lib/bespoke/xsltproc_spec.rb
200
241
  - spec/spec_helper.rb
201
- - spec/template_spec.rb
202
- - spec/xsltproc_spec.rb
242
+ - spec/support/fixture_helpers.rb
203
243
  has_rdoc: