bespoke 0.2.0 → 0.2.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.
@@ -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: