iostreams 0.14.0 → 0.15.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +202 -0
  3. data/README.md +155 -47
  4. data/lib/io_streams/file/reader.rb +7 -8
  5. data/lib/io_streams/file/writer.rb +7 -8
  6. data/lib/io_streams/io_streams.rb +313 -129
  7. data/lib/io_streams/{delimited → line}/reader.rb +20 -30
  8. data/lib/io_streams/line/writer.rb +81 -0
  9. data/lib/io_streams/pgp.rb +4 -14
  10. data/lib/io_streams/record/reader.rb +55 -0
  11. data/lib/io_streams/record/writer.rb +63 -0
  12. data/lib/io_streams/row/reader.rb +60 -0
  13. data/lib/io_streams/row/writer.rb +62 -0
  14. data/lib/io_streams/s3.rb +25 -0
  15. data/lib/io_streams/s3/reader.rb +64 -0
  16. data/lib/io_streams/s3/writer.rb +13 -0
  17. data/lib/io_streams/streams.rb +1 -1
  18. data/lib/io_streams/tabular.rb +163 -0
  19. data/lib/io_streams/tabular/errors.rb +14 -0
  20. data/lib/io_streams/tabular/header.rb +146 -0
  21. data/lib/io_streams/tabular/parser/array.rb +26 -0
  22. data/lib/io_streams/tabular/parser/base.rb +12 -0
  23. data/lib/io_streams/tabular/parser/csv.rb +35 -0
  24. data/lib/io_streams/tabular/parser/fixed.rb +88 -0
  25. data/lib/io_streams/tabular/parser/hash.rb +21 -0
  26. data/lib/io_streams/tabular/parser/json.rb +25 -0
  27. data/lib/io_streams/tabular/parser/psv.rb +34 -0
  28. data/lib/io_streams/tabular/utility/csv_row.rb +115 -0
  29. data/lib/io_streams/version.rb +2 -2
  30. data/lib/io_streams/xlsx/reader.rb +1 -1
  31. data/lib/io_streams/zip/reader.rb +1 -1
  32. data/lib/io_streams/zip/writer.rb +1 -1
  33. data/lib/iostreams.rb +21 -10
  34. data/test/bzip2_reader_test.rb +21 -22
  35. data/test/bzip2_writer_test.rb +38 -32
  36. data/test/file_reader_test.rb +19 -18
  37. data/test/file_writer_test.rb +23 -22
  38. data/test/files/test.json +3 -0
  39. data/test/gzip_reader_test.rb +21 -22
  40. data/test/gzip_writer_test.rb +35 -29
  41. data/test/io_streams_test.rb +137 -61
  42. data/test/line_reader_test.rb +105 -0
  43. data/test/line_writer_test.rb +50 -0
  44. data/test/pgp_reader_test.rb +29 -29
  45. data/test/pgp_test.rb +149 -195
  46. data/test/pgp_writer_test.rb +63 -62
  47. data/test/record_reader_test.rb +61 -0
  48. data/test/record_writer_test.rb +73 -0
  49. data/test/row_reader_test.rb +34 -0
  50. data/test/row_writer_test.rb +51 -0
  51. data/test/tabular_test.rb +184 -0
  52. data/test/xlsx_reader_test.rb +13 -17
  53. data/test/zip_reader_test.rb +21 -22
  54. data/test/zip_writer_test.rb +40 -36
  55. metadata +41 -17
  56. data/lib/io_streams/csv/reader.rb +0 -21
  57. data/lib/io_streams/csv/writer.rb +0 -20
  58. data/lib/io_streams/delimited/writer.rb +0 -67
  59. data/test/csv_reader_test.rb +0 -34
  60. data/test/csv_writer_test.rb +0 -35
  61. data/test/delimited_reader_test.rb +0 -115
  62. data/test/delimited_writer_test.rb +0 -44
@@ -1,92 +1,93 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- module Streams
4
- class PgpWriterTest < Minitest::Test
5
- describe IOStreams::Pgp::Writer do
6
- before do
7
- file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
8
- @data = File.read(file_name)
3
+ class PgpWriterTest < Minitest::Test
4
+ describe IOStreams::Pgp::Writer do
5
+ let :temp_file do
6
+ Tempfile.new('iostreams')
7
+ end
9
8
 
10
- @temp_file = Tempfile.new('iostreams')
11
- @file_name = @temp_file.to_path
12
- end
9
+ let :file_name do
10
+ temp_file.path
11
+ end
13
12
 
14
- after do
15
- @temp_file.delete if @temp_file
16
- end
13
+ let :decrypted do
14
+ file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
15
+ File.read(file_name)
16
+ end
17
17
 
18
- describe '.open' do
19
- it 'writes encrypted text file' do
20
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', binary: false) do |io|
21
- io.write(@data)
22
- end
18
+ after do
19
+ temp_file.delete
20
+ end
23
21
 
24
- result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase', binary: false) { |file| file.read }
25
- assert_equal @data, result
22
+ describe '.open' do
23
+ it 'writes encrypted text file' do
24
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'receiver@example.org', binary: false) do |io|
25
+ io.write(decrypted)
26
26
  end
27
27
 
28
- it 'writes encrypted binary file' do
29
- binary_file_name = File.join(File.dirname(__FILE__), 'files', 'spreadsheet.xlsx')
30
- binary_data = File.open(binary_file_name, 'rb') { |file| file.read }
28
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase', binary: false) { |file| file.read }
29
+ assert_equal decrypted, result
30
+ end
31
31
 
32
- File.open(binary_file_name, 'rb') do |input|
33
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org') do |output|
34
- IOStreams.copy(input, output, 65535)
35
- end
36
- end
32
+ it 'writes encrypted binary file' do
33
+ binary_file_name = File.join(File.dirname(__FILE__), 'files', 'spreadsheet.xlsx')
34
+ binary_data = File.open(binary_file_name, 'rb') { |file| file.read }
37
35
 
38
- result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase') { |file| file.read }
39
- assert_equal binary_data, result
36
+ File.open(binary_file_name, 'rb') do |input|
37
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'receiver@example.org') do |output|
38
+ IOStreams.copy(input, output)
39
+ end
40
40
  end
41
41
 
42
- it 'writes and signs encrypted file' do
43
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
44
- io.write(@data)
45
- end
42
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase') { |file| file.read }
43
+ assert_equal binary_data, result
44
+ end
46
45
 
47
- result = IOStreams::Pgp::Reader.open(@file_name, passphrase: 'receiver_passphrase') { |file| file.read }
48
- assert_equal @data, result
46
+ it 'writes and signs encrypted file' do
47
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
48
+ io.write(decrypted)
49
49
  end
50
50
 
51
- it 'fails with bad signer passphrase' do
52
- skip 'GnuPG v2.1 and above passes when it should not' if IOStreams::Pgp.pgp_version.to_f >= 2.1
53
- assert_raises IOStreams::Pgp::Failure do
54
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'BAD') do |io|
55
- io.write(@data)
56
- end
51
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase') { |file| file.read }
52
+ assert_equal decrypted, result
53
+ end
54
+
55
+ it 'fails with bad signer passphrase' do
56
+ skip 'GnuPG v2.1 and above passes when it should not' if IOStreams::Pgp.pgp_version.to_f >= 2.1
57
+ assert_raises IOStreams::Pgp::Failure do
58
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'BAD') do |io|
59
+ io.write(decrypted)
57
60
  end
58
61
  end
62
+ end
59
63
 
60
- it 'fails with bad recipient' do
61
- assert_raises IOStreams::Pgp::Failure do
62
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'BAD@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
63
- io.write(@data)
64
- # Allow process to terminate
65
- sleep 1
66
- io.write(@data)
67
- end
64
+ it 'fails with bad recipient' do
65
+ assert_raises IOStreams::Pgp::Failure do
66
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'BAD@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
67
+ io.write(decrypted)
68
+ # Allow process to terminate
69
+ sleep 1
70
+ io.write(decrypted)
68
71
  end
69
72
  end
73
+ end
70
74
 
71
- it 'fails with bad signer' do
72
- assert_raises IOStreams::Pgp::Failure do
73
- IOStreams::Pgp::Writer.open(@file_name, recipient: 'receiver@example.org', signer: 'BAD@example.org', signer_passphrase: 'sender_passphrase') do |io|
74
- io.write(@data)
75
- end
75
+ it 'fails with bad signer' do
76
+ assert_raises IOStreams::Pgp::Failure do
77
+ IOStreams::Pgp::Writer.open(file_name, recipient: 'receiver@example.org', signer: 'BAD@example.org', signer_passphrase: 'sender_passphrase') do |io|
78
+ io.write(decrypted)
76
79
  end
77
80
  end
81
+ end
78
82
 
79
- it 'fails with stream output' do
80
- string_io = StringIO.new
81
- assert_raises NotImplementedError do
82
- IOStreams::Pgp::Writer.open(string_io, recipient: 'receiver@example.org') do |io|
83
- io.write(@data)
84
- end
83
+ it 'fails with stream output' do
84
+ string_io = StringIO.new
85
+ assert_raises NotImplementedError do
86
+ IOStreams::Pgp::Writer.open(string_io, recipient: 'receiver@example.org') do |io|
87
+ io.write(decrypted)
85
88
  end
86
89
  end
87
-
88
90
  end
89
-
90
91
  end
91
92
  end
92
93
  end
@@ -0,0 +1,61 @@
1
+ require_relative 'test_helper'
2
+
3
+ class RecordReaderTest < Minitest::Test
4
+ describe IOStreams::Record::Reader do
5
+ let :file_name do
6
+ File.join(File.dirname(__FILE__), 'files', 'test.csv')
7
+ end
8
+
9
+ let :json_file_name do
10
+ File.join(File.dirname(__FILE__), 'files', 'test.json')
11
+ end
12
+
13
+ let :csv_rows do
14
+ CSV.read(file_name)
15
+ end
16
+
17
+ let :expected do
18
+ rows = csv_rows.dup
19
+ header = rows.shift
20
+ rows.collect { |row| header.zip(row).to_h }
21
+ end
22
+
23
+ describe '#each' do
24
+ it 'csv file' do
25
+ records = []
26
+ IOStreams::Record::Reader.open(file_name, cleanse_header: false) do |io|
27
+ io.each { |row| records << row }
28
+ end
29
+ assert_equal expected, records
30
+ end
31
+
32
+ it 'json file' do
33
+ records = []
34
+ IOStreams::Record::Reader.open(json_file_name, cleanse_header: false) do |input|
35
+ input.each { |row| records << row }
36
+ end
37
+ assert_equal expected, records
38
+ end
39
+
40
+ it 'stream' do
41
+ rows = []
42
+ IOStreams.line_reader(file_name) do |file|
43
+ IOStreams::Record::Reader.open(file, cleanse_header: false) do |io|
44
+ io.each { |row| rows << row }
45
+ end
46
+ end
47
+ assert_equal expected, rows
48
+ end
49
+ end
50
+
51
+ describe '#collect' do
52
+ it 'json file' do
53
+ records = IOStreams::Record::Reader.open(json_file_name) do |input|
54
+ input.collect { |record| record["state"] }
55
+ end
56
+ assert_equal expected.collect { |record| record["state"] }, records
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,73 @@
1
+ require_relative 'test_helper'
2
+ require 'csv'
3
+
4
+ class RecordWriterTest < Minitest::Test
5
+ describe IOStreams::Record::Writer do
6
+ let :csv_file_name do
7
+ File.join(File.dirname(__FILE__), 'files', 'test.csv')
8
+ end
9
+
10
+ let :json_file_name do
11
+ File.join(File.dirname(__FILE__), 'files', 'test.json')
12
+ end
13
+
14
+ let :raw_csv_data do
15
+ File.read(csv_file_name)
16
+ end
17
+
18
+ let :raw_json_data do
19
+ File.read(json_file_name)
20
+ end
21
+
22
+ let :csv_rows do
23
+ CSV.read(csv_file_name)
24
+ end
25
+
26
+ let :inputs do
27
+ rows = csv_rows.dup
28
+ header = rows.shift
29
+ rows.collect { |row| header.zip(row).to_h }
30
+ end
31
+
32
+ let :temp_file do
33
+ Tempfile.new('iostreams')
34
+ end
35
+
36
+ let :file_name do
37
+ temp_file.path
38
+ end
39
+
40
+ after do
41
+ temp_file.delete
42
+ end
43
+
44
+ describe '#<<' do
45
+ it 'file' do
46
+ IOStreams::Record::Writer.open(file_name) do |io|
47
+ inputs.each { |hash| io << hash }
48
+ end
49
+ result = File.read(file_name)
50
+ assert_equal raw_csv_data, result
51
+ end
52
+
53
+ it 'json file' do
54
+ IOStreams::Record::Writer.open(file_name, file_name: 'abc.json') do |io|
55
+ inputs.each { |hash| io << hash }
56
+ end
57
+ result = File.read(file_name)
58
+ assert_equal raw_json_data, result
59
+ end
60
+
61
+ it 'stream' do
62
+ io_string = StringIO.new
63
+ IOStreams::Line::Writer.open(io_string) do |io|
64
+ IOStreams::Record::Writer.open(io) do |stream|
65
+ inputs.each { |row| stream << row }
66
+ end
67
+ end
68
+ assert_equal raw_csv_data, io_string.string
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'test_helper'
2
+
3
+ class RowReaderTest < Minitest::Test
4
+ describe IOStreams::Row::Reader do
5
+ let :file_name do
6
+ File.join(File.dirname(__FILE__), 'files', 'test.csv')
7
+ end
8
+
9
+ let :expected do
10
+ CSV.read(file_name)
11
+ end
12
+
13
+ describe '.open' do
14
+ it 'file' do
15
+ rows = []
16
+ IOStreams::Row::Reader.open(file_name) do |io|
17
+ io.each { |row| rows << row }
18
+ end
19
+ assert_equal expected, rows
20
+ end
21
+
22
+ it 'stream' do
23
+ rows = []
24
+ IOStreams.line_reader(file_name) do |file|
25
+ IOStreams::Row::Reader.open(file) do |io|
26
+ io.each { |row| rows << row }
27
+ end
28
+ end
29
+ assert_equal expected, rows
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ require_relative 'test_helper'
2
+ require 'csv'
3
+
4
+ class RowWriterTest < Minitest::Test
5
+ describe IOStreams::Row::Writer do
6
+ let :csv_file_name do
7
+ File.join(File.dirname(__FILE__), 'files', 'test.csv')
8
+ end
9
+
10
+ let :raw_csv_data do
11
+ File.read(csv_file_name)
12
+ end
13
+
14
+ let :csv_rows do
15
+ CSV.read(csv_file_name)
16
+ end
17
+
18
+ let :temp_file do
19
+ Tempfile.new('iostreams')
20
+ end
21
+
22
+ let :file_name do
23
+ temp_file.path
24
+ end
25
+
26
+ after do
27
+ temp_file.delete
28
+ end
29
+
30
+ describe '.open' do
31
+ it 'file' do
32
+ IOStreams::Row::Writer.open(file_name) do |io|
33
+ csv_rows.each { |array| io << array }
34
+ end
35
+ result = File.read(file_name)
36
+ assert_equal raw_csv_data, result
37
+ end
38
+
39
+ it 'stream' do
40
+ io_string = StringIO.new
41
+ IOStreams::Line::Writer.open(io_string) do |io|
42
+ IOStreams::Row::Writer.open(io) do |stream|
43
+ csv_rows.each { |array| stream << array }
44
+ end
45
+ end
46
+ assert_equal raw_csv_data, io_string.string
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,184 @@
1
+ require_relative 'test_helper'
2
+
3
+ class TabularTest < Minitest::Test
4
+ describe IOStreams::Tabular do
5
+ let :format do
6
+ :csv
7
+ end
8
+
9
+ let :tabular do
10
+ IOStreams::Tabular.new(columns: ['first_field', 'second', 'third'], format: format)
11
+ end
12
+
13
+ describe '#parse_header' do
14
+ it 'parses and sets the csv header' do
15
+ tabular = IOStreams::Tabular.new(format: :csv)
16
+ header = tabular.parse_header('first field,Second,thirD')
17
+ assert_equal ['first field', 'Second', 'thirD'], header
18
+ assert_equal header, tabular.header.columns
19
+ end
20
+ end
21
+
22
+ describe '#cleanse_header!' do
23
+ describe 'cleanses' do
24
+ it 'a csv header' do
25
+ tabular = IOStreams::Tabular.new(columns: ['first field', 'Second', 'thirD'])
26
+ header = tabular.cleanse_header!
27
+ assert_equal ['first_field', "second", "third"], header
28
+ assert_equal header, tabular.header.columns
29
+ end
30
+
31
+ it 'white listed snake cased alphanumeric columns' do
32
+ tabular = IOStreams::Tabular.new(
33
+ columns: ['Ard Vark', 'password', 'robot version', '$$$'],
34
+ allowed_columns: %w( ard_vark robot_version )
35
+ )
36
+ expected_header = ['ard_vark', nil, 'robot_version', nil]
37
+ cleansed_header = tabular.cleanse_header!
38
+ assert_equal(expected_header, cleansed_header)
39
+ end
40
+ end
41
+
42
+ describe 'allowed_columns' do
43
+ before do
44
+ @allowed_columns = ['first', 'second', 'third', 'fourth', 'fifth']
45
+ end
46
+
47
+ it 'passes' do
48
+ tabular = IOStreams::Tabular.new(columns: [' first ', 'Second', 'thirD '], allowed_columns: @allowed_columns)
49
+ header = tabular.cleanse_header!
50
+ assert_equal ['first', 'second', 'third'], header
51
+ assert_equal header, tabular.header.columns
52
+ assert_equal @allowed_columns, tabular.header.allowed_columns
53
+ end
54
+
55
+ it 'nils columns not in the whitelist' do
56
+ tabular = IOStreams::Tabular.new(columns: [' first ', 'Unknown Column', 'thirD '], allowed_columns: @allowed_columns)
57
+ header = tabular.cleanse_header!
58
+ assert_equal ['first', nil, 'third'], header
59
+ end
60
+
61
+ it 'raises exception for columns not in the whitelist' do
62
+ tabular = IOStreams::Tabular.new(columns: [' first ', 'Unknown Column', 'thirD '], allowed_columns: @allowed_columns, skip_unknown: false)
63
+ exc = assert_raises IOStreams::Tabular::Errors::InvalidHeader do
64
+ tabular.cleanse_header!
65
+ end
66
+ assert_equal 'Unknown columns after cleansing: Unknown Column', exc.message
67
+ end
68
+
69
+ it 'raises exception missing required columns' do
70
+ required = ['first', 'second', 'fifth']
71
+ tabular = IOStreams::Tabular.new(columns: [' first ', 'Second', 'thirD '], allowed_columns: @allowed_columns, required_columns: required)
72
+ exc = assert_raises IOStreams::Tabular::Errors::InvalidHeader do
73
+ tabular.cleanse_header!
74
+ end
75
+ assert_equal 'Missing columns after cleansing: fifth', exc.message
76
+ end
77
+
78
+ it 'raises exception when no columns left' do
79
+ tabular = IOStreams::Tabular.new(columns: ['one', 'two', 'three'], allowed_columns: @allowed_columns)
80
+ exc = assert_raises IOStreams::Tabular::Errors::InvalidHeader do
81
+ tabular.cleanse_header!
82
+ end
83
+ assert_equal 'All columns are unknown after cleansing: one,two,three', exc.message
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#record_parse' do
89
+ describe ':array format' do
90
+ let :format do
91
+ :array
92
+ end
93
+
94
+ it 'renders' do
95
+ assert hash = tabular.record_parse([1, 2, 3])
96
+ assert_equal({'first_field' => 1, 'second' => 2, 'third' => 3}, hash)
97
+ end
98
+ end
99
+
100
+ it 'format :csv' do
101
+ assert hash = tabular.record_parse('1,2,3')
102
+ assert_equal({'first_field' => '1', 'second' => '2', 'third' => '3'}, hash)
103
+ end
104
+
105
+ describe ':hash format' do
106
+ let :format do
107
+ :hash
108
+ end
109
+
110
+ it 'renders' do
111
+ assert hash = tabular.record_parse('first_field' => 1, 'second' => 2, 'third' => 3)
112
+ assert_equal({'first_field' => 1, 'second' => 2, 'third' => 3}, hash)
113
+ end
114
+ end
115
+
116
+ describe ':json format' do
117
+ let :format do
118
+ :json
119
+ end
120
+
121
+ it 'renders' do
122
+ assert hash = tabular.record_parse('{"first_field":1,"second":2,"third":3}')
123
+ assert_equal({'first_field' => 1, 'second' => 2, 'third' => 3}, hash)
124
+ end
125
+ end
126
+
127
+ describe ':psv format' do
128
+ let :format do
129
+ :psv
130
+ end
131
+
132
+ it 'renders' do
133
+ assert hash = tabular.record_parse('1|2|3')
134
+ assert_equal({'first_field' => '1', 'second' => '2', 'third' => '3'}, hash)
135
+ end
136
+ end
137
+
138
+ it 'skips columns not in the whitelist' do
139
+ tabular.header.allowed_columns = ['first', 'second', 'third', 'fourth', 'fifth']
140
+ tabular.cleanse_header!
141
+ assert hash = tabular.record_parse('1,2,3')
142
+ assert_equal({'second' => '2', 'third' => '3'}, hash)
143
+ end
144
+
145
+ it 'handles missing values' do
146
+ assert hash = tabular.record_parse('1,2')
147
+ assert_equal({'first_field' => '1', 'second' => '2', 'third' => nil}, hash)
148
+ end
149
+ end
150
+
151
+ describe '#render' do
152
+ it 'renders an array of values' do
153
+ assert csv_string = tabular.render([5, 6, 9])
154
+ assert_equal '5,6,9', csv_string
155
+ end
156
+
157
+ it 'renders a hash' do
158
+ assert csv_string = tabular.render({'third' => '3', 'first_field' => '1'})
159
+ assert_equal '1,,3', csv_string
160
+ end
161
+
162
+ it 'renders a hash including nil and boolean' do
163
+ assert csv_string = tabular.render({'third' => true, 'first_field' => false, 'second' => nil})
164
+ assert_equal 'false,,true', csv_string
165
+ end
166
+
167
+ describe ':psv format' do
168
+ let :format do
169
+ :psv
170
+ end
171
+
172
+ it 'renders psv nil and boolean' do
173
+ assert psv_string = tabular.render({'third' => true, 'first_field' => false, 'second' => nil})
174
+ assert_equal 'false||true', psv_string
175
+ end
176
+
177
+ it 'renders psv numeric and pipe data' do
178
+ assert psv_string = tabular.render({'third' => 23, 'first_field' => 'a|b|c', 'second' => '|'})
179
+ assert_equal 'a:b:c|:|23', psv_string
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end