transit-ruby 0.8.552-java

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,94 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ module Transit
18
+ describe RollingCache do
19
+ describe 'writing' do
20
+ it 'returns the value the first time it sees it' do
21
+ assert { RollingCache.new.write('abcd') == 'abcd' }
22
+ end
23
+
24
+ it 'returns a key the 2nd thru n times it sees a value' do
25
+ rc = RollingCache.new
26
+ assert { rc.write('abcd') == 'abcd' }
27
+
28
+ key = rc.write('abcd')
29
+ assert { key != 'abcd' }
30
+
31
+ 100.times do
32
+ assert { rc.write('abcd') == key }
33
+ end
34
+ end
35
+
36
+ it 'can handle CACHE_SIZE different values' do
37
+ rc = RollingCache.new
38
+ RollingCache::CACHE_SIZE.times do |i|
39
+ assert { rc.write("value#{i}") == "value#{i}" }
40
+ end
41
+
42
+ assert { rc.size == RollingCache::CACHE_SIZE }
43
+ end
44
+
45
+ it 'resets after CACHE_SIZE different values' do
46
+ rc = RollingCache.new
47
+ (RollingCache::CACHE_SIZE+1).times do |i|
48
+ assert{ rc.write("value#{i}") == "value#{i}" }
49
+ end
50
+
51
+ assert { rc.size == 1 }
52
+ end
53
+ end
54
+
55
+ describe ".cacheable?" do
56
+ it 'returns false for small strings' do
57
+ cache = RollingCache.new
58
+
59
+ names = random_strings(3, 500)
60
+ 1000.times do |i|
61
+ name = names.sample
62
+ assert { !cache.cacheable?(name, false) }
63
+ assert { !cache.cacheable?(name, true) }
64
+ end
65
+ end
66
+
67
+ it 'returns false for non map-keys' do
68
+ cache = RollingCache.new
69
+
70
+ names = random_strings(200, 500)
71
+ 1000.times do |i|
72
+ name = names.sample
73
+ assert { !cache.cacheable?(name, false) }
74
+ end
75
+ end
76
+ end
77
+
78
+ describe ".cache_key?" do
79
+ it 'special cases map-as-array key as false' do
80
+ cache = RollingCache.new
81
+ assert { !cache.cache_key?(Transit::MAP_AS_ARRAY) }
82
+ end
83
+ end
84
+
85
+ describe 'reading' do
86
+ it 'returns the value, given a key that has a value in the cache' do
87
+ rc = RollingCache.new
88
+ rc.write 'abcd'
89
+ key = rc.write 'abcd'
90
+ assert { rc.read(key) == 'abcd' }
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,172 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+
17
+ def nan?(obj)
18
+ obj.respond_to?(:nan?) and obj.nan?
19
+ end
20
+
21
+ def round_trip(obj, type, opts={})
22
+ obj_before = obj
23
+
24
+ io = StringIO.new('', 'w+')
25
+ writer = Transit::Writer.new(type, io, :handlers => opts[:write_handlers])
26
+ writer.write(obj)
27
+
28
+ # ensure that we don't modify the object being written
29
+ if nan?(obj_before)
30
+ assert { obj.nan? }
31
+ else
32
+ assert { obj == obj_before }
33
+ end
34
+ reader = Transit::Reader.new(type, StringIO.new(io.string), :handlers => opts[:read_handlers])
35
+ reader.read
36
+ end
37
+
38
+ def assert_equal_times(actual,expected)
39
+ return false unless expected.is_a?(Date) || expected.is_a?(Time) || expected.is_a?(DateTime)
40
+ assert { Transit::DateTimeUtil.to_millis(actual) == Transit::DateTimeUtil.to_millis(expected) }
41
+ assert { actual.zone == expected.zone }
42
+ end
43
+
44
+ def assert_nan(actual,expected)
45
+ return false unless nan?(expected)
46
+ expect(actual.respond_to?(:nan?)).to eq(true)
47
+ expect(actual.nan?).to eq(true)
48
+ end
49
+
50
+ def validate(expected, actual)
51
+ assert_equal_times(actual, expected) || assert_nan(actual, expected) || (expect(actual).to eq(expected))
52
+ end
53
+
54
+ def round_trips(label, obj, type, opts={})
55
+ expected = opts[:expected] || obj
56
+
57
+ it "round trips #{label} at top level", :focus => !!opts[:focus], :pending => opts[:pending] do
58
+ validate(expected, round_trip(obj, type, opts))
59
+ end
60
+
61
+ it "round trips #{label} as a map key", :focus => !!opts[:focus], :pending => opts[:pending] do
62
+ validate(expected, round_trip({obj => 0}, type, opts).keys.first)
63
+ end
64
+
65
+ it "round trips #{label} as a map value", :focus => !!opts[:focus], :pending => opts[:pending] do
66
+ validate(expected, round_trip({a: obj}, type, opts).values.first)
67
+ end
68
+
69
+ it "round trips #{label} as an array value", :focus => !!opts[:focus], :pending => opts[:pending] do
70
+ validate(expected, round_trip([obj], type, opts).first)
71
+ end
72
+ end
73
+
74
+ module Transit
75
+ PhoneNumber = Struct.new(:area, :prefix, :suffix)
76
+ def PhoneNumber.parse(p)
77
+ area, prefix, suffix = p.split(".")
78
+ PhoneNumber.new(area, prefix, suffix)
79
+ end
80
+
81
+ class PhoneNumberHandler
82
+ def tag(_) "P" end
83
+ def rep(p) "#{p.area}.#{p.prefix}.#{p.suffix}" end
84
+ def string_rep(p) rep(p) end
85
+ end
86
+
87
+ class PhoneNumberReadHandler
88
+ def from_rep(v) PhoneNumber.parse(v) end
89
+ end
90
+
91
+ class PersonReadHandler
92
+ def from_rep(v)
93
+ Person.new(v[:first_name],v[:last_name],v[:birthdate])
94
+ end
95
+ end
96
+
97
+ shared_examples "round trips" do |type|
98
+ round_trips("nil", nil, type)
99
+ round_trips("a keyword", random_symbol, type)
100
+ round_trips("a string", random_string, type)
101
+ round_trips("a string starting with ~", "~#{random_string}", type)
102
+ round_trips("a string starting with ^", "^#{random_string}", type)
103
+ round_trips("a string starting with `", "`#{random_string}", type)
104
+ round_trips("true", true, type)
105
+ round_trips("false", false, type)
106
+ round_trips("a small int", 1, type)
107
+ round_trips("a big int", 123456789012345, type)
108
+ round_trips("a very big int", 123456789012345679012345678890, type)
109
+ round_trips("a float", 1234.56, type)
110
+ round_trips("NaN", Float::NAN, type)
111
+ round_trips("Infinity", Float::INFINITY, type)
112
+ round_trips("-Infinity", -Float::INFINITY, type)
113
+ round_trips("a bigdec", BigDecimal.new("123.45"), type)
114
+ round_trips("an instant (DateTime local)", DateTime.new(2014,1,2,3,4,5, "-5"), type,
115
+ :expected => DateTime.new(2014,1,2, (3+5) ,4,5, "+00:00"))
116
+ round_trips("an instant (DateTime gmt)", DateTime.new(2014,1,2,3,4,5), type)
117
+ round_trips("an instant (Time local)", Time.new(2014,1,2,3,4,5, "-05:00"), type,
118
+ :expected => DateTime.new(2014,1,2, (3+5) ,4,5, "+00:00"))
119
+ round_trips("an instant (Time gmt)", Time.new(2014,1,2,3,4,5, "+00:00"), type,
120
+ :expected => DateTime.new(2014,1,2,3,4,5, "+00:00"))
121
+ round_trips("a Date", Date.new(2014,1,2), type, :expected => DateTime.new(2014,1,2))
122
+ round_trips("a uuid", UUID.new, type)
123
+ round_trips("a link", Link.new(Addressable::URI.parse("http://example.org/search"), "search"), type)
124
+ round_trips("a link", Link.new(Addressable::URI.parse("http://example.org/search"), "search", nil, "image"), type)
125
+ round_trips("a link with string uri", Link.new("http://example.org/search", "search", nil, "image"), type)
126
+ round_trips("a uri (url)", Addressable::URI.parse("http://example.com"), type)
127
+ round_trips("a uri (file)", Addressable::URI.parse("file:///path/to/file.txt"), type)
128
+ round_trips("a bytearray", ByteArray.new(random_string(50)), type)
129
+ round_trips("a Transit::Symbol", Transit::Symbol.new(random_string), type)
130
+ round_trips("a hash w/ stringable keys", {"this" => "~hash", "1" => 2}, type)
131
+ round_trips("a set", Set.new([1,2,3]), type)
132
+ round_trips("a set of sets", Set.new([Set.new([1,2]), Set.new([3,4])]), type)
133
+ round_trips("an array", [1,2,3], type)
134
+ round_trips("a char", TaggedValue.new("c", "x"), type, :expected => "x")
135
+ round_trips("a list", TaggedValue.new("list", [1,2,3]), type, :expected => [1,2,3])
136
+ round_trips("an array of maps w/ cacheable keys", [{"this" => "a"},{"this" => "b"}], type)
137
+
138
+ round_trips("edge case chars", %w[` ~ ^ #], type)
139
+
140
+ round_trips("an extension scalar", PhoneNumber.new("555","867","5309"), type,
141
+ :write_handlers => {PhoneNumber => PhoneNumberHandler.new},
142
+ :read_handlers => {"P" => PhoneNumberReadHandler.new})
143
+ round_trips("an extension struct", Person.new("First","Last",:today), type,
144
+ :write_handlers => {Person => PersonHandler.new},
145
+ :read_handlers => {"person" => PersonReadHandler.new})
146
+ round_trips("a hash with simple values", {'a' => 1, 'b' => 2, 'name' => 'russ'}, type)
147
+ round_trips("a hash with Transit::Symbols", {Transit::Symbol.new("foo") => Transit::Symbol.new("bar")}, type)
148
+ round_trips("a hash with 53 bit ints", {2**53-1 => 2**53-2}, type)
149
+ round_trips("a hash with 54 bit ints", {2**53 => 2**53+1}, type)
150
+ round_trips("a map with composite keys", {{a: :b} => {c: :d}}, type)
151
+ round_trips("a TaggedValue", TaggedValue.new("unrecognized",:value), type)
152
+ round_trips("an unrecognized hash encoding", {"~#unrecognized" => :value}, type)
153
+ round_trips("an unrecognized string encoding", "~Xunrecognized", type)
154
+
155
+ round_trips("a nested structure (map on top)", {a: [1, [{b: "~c"}]]}, type)
156
+ round_trips("a nested structure (array on top)", [37, {a: [1, [{b: "~c"}]]}], type)
157
+ round_trips("a map that looks like transit data", [{"~#set"=>[1,2,3]},{"~#set"=>[4,5,6]}], type)
158
+ round_trips("a ratio of big value", [{"~#ratio"=>["~n36893488147419103231","~n73786976294838206463"]}], type)
159
+ end
160
+
161
+ describe "Transit using json" do
162
+ include_examples "round trips", :json
163
+ end
164
+
165
+ describe "Transit using json_verbose" do
166
+ include_examples "round trips", :json_verbose
167
+ end
168
+
169
+ describe "Transit using msgpack" do
170
+ include_examples "round trips", :msgpack
171
+ end
172
+ end
@@ -0,0 +1,136 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2014 Cognitect. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS-IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'spec_helper'
17
+
18
+ module Transit
19
+ describe Transit::Symbol do
20
+ it 'can be made from a symbol' do
21
+ 500.times do
22
+ sym = random_symbol
23
+ assert { Transit::Symbol.new(sym).to_sym == sym }
24
+ end
25
+ end
26
+
27
+ it 'can be made from a string' do
28
+ 500.times do
29
+ str = random_string
30
+ assert { Transit::Symbol.new(str).to_sym == str.to_sym }
31
+ end
32
+ end
33
+
34
+ it 'is equal to another rendition of itself' do
35
+ 500.times do
36
+ sym = random_symbol
37
+ assert { Transit::Symbol.new(sym) == Transit::Symbol.new(sym)}
38
+ end
39
+ end
40
+
41
+ it 'behaves as a hash key' do
42
+ keys = Set.new(Array.new(1000).map {|x| random_symbol})
43
+
44
+ test_hash = {}
45
+ keys.each_with_index {|k, i| test_hash[Transit::Symbol.new(k)] = i}
46
+
47
+ keys.each_with_index do |k, i|
48
+ new_key = Transit::Symbol.new(k)
49
+ value = test_hash[new_key]
50
+ assert { value == i }
51
+ end
52
+ end
53
+
54
+ it "provides namespace" do
55
+ assert { Transit::Symbol.new("foo/bar").namespace == "foo" }
56
+ assert { Transit::Symbol.new("foo").namespace == nil }
57
+ end
58
+
59
+ it "provides name" do
60
+ assert { Transit::Symbol.new("foo").name == "foo" }
61
+ assert { Transit::Symbol.new("foo/bar").name == "bar" }
62
+ end
63
+
64
+ it "special cases '/'" do
65
+ assert { Transit::Symbol.new("/").name == "/" }
66
+ assert { Transit::Symbol.new("/").namespace == nil }
67
+ end
68
+ end
69
+
70
+ describe UUID do
71
+ it 'round trips strings' do
72
+ 10.times do
73
+ uuid = UUID.random
74
+ assert { UUID.new(uuid.to_s) == uuid }
75
+ end
76
+ end
77
+
78
+ it 'round trips ints' do
79
+ 10.times do
80
+ uuid = UUID.random
81
+ assert { UUID.new(uuid.most_significant_bits, uuid.least_significant_bits) == uuid }
82
+ end
83
+ end
84
+ end
85
+
86
+ describe Link do
87
+ let(:href) { Addressable::URI.parse("http://example.org/search") }
88
+ let(:string_href) { "http://example.org/search" }
89
+ let(:rel) { "search" }
90
+ let(:prompt) { "Enter search string" }
91
+ let(:name) { "this is my name" }
92
+
93
+ it 'can be made from some given arugments' do
94
+ link = Link.new(href, rel)
95
+ assert { link.href == href }
96
+ assert { link.rel == rel }
97
+ assert { link.prompt == nil }
98
+ assert { link.name == nil }
99
+ assert { link.render == nil }
100
+ end
101
+
102
+ it 'can be made from all 5 given correct arguments' do
103
+ link = Link.new(href, rel, name, "Image", prompt)
104
+ assert { link.href == href }
105
+ assert { link.rel == rel }
106
+ assert { link.name == name }
107
+ assert { link.render == "image" }
108
+ assert { link.prompt == prompt }
109
+ end
110
+
111
+ it 'can be made with uri in string' do
112
+ link = Link.new(string_href, rel)
113
+ assert { link.href == Addressable::URI.parse(string_href) }
114
+ end
115
+
116
+ it 'raises exception if href and rel are not given' do
117
+ assert { rescuing { Link.new }.is_a? ArgumentError }
118
+ assert { rescuing { Link.new("foo") }.is_a? ArgumentError }
119
+ end
120
+
121
+ it 'raises exception if render is not a valid value (link|image)' do
122
+ assert { rescuing { Link.new(href, rel, nil, "document") }.is_a? ArgumentError }
123
+ end
124
+
125
+ it 'leaves the input map alone' do
126
+ input = {"href" => "http://example.com", "rel" => "???", "render" => "LINK"}
127
+ Link.new(input)
128
+ assert { input["href"] == "http://example.com" }
129
+ assert { input["render"] == "LINK" }
130
+ end
131
+
132
+ it 'produces a frozen map' do
133
+ assert { Link.new("/path", "the-rel").to_h.frozen? }
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,296 @@
1
+ # Copyright 2014 Cognitect. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS-IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+ require 'json'
17
+
18
+ module Transit
19
+ describe Writer do
20
+ let(:io) { StringIO.new('', 'w+') }
21
+ let(:writer) { Writer.new(:json_verbose, io) }
22
+
23
+ describe "marshaling transit types" do
24
+ def self.bytes
25
+ @bytes ||= SecureRandom.random_bytes
26
+ end
27
+
28
+ def self.marshals_scalar(label, value, rep, opts={})
29
+ it "marshals #{label}", :focus => opts[:focus] do
30
+ writer.write(value)
31
+ assert { JSON.parse(io.string) == {"~#'" => rep} }
32
+ end
33
+ end
34
+
35
+ def self.marshals_structure(label, value, rep, opts={})
36
+ it "marshals #{label}", :focus => opts[:focus] do
37
+ writer.write(value)
38
+ assert { JSON.parse(io.string) == rep }
39
+ end
40
+ end
41
+
42
+ marshals_scalar("a UUID",
43
+ UUID.new("dda5a83f-8f9d-4194-ae88-5745c8ca94a7"),
44
+ "~udda5a83f-8f9d-4194-ae88-5745c8ca94a7")
45
+ marshals_scalar("a Transit::Symbol", Transit::Symbol.new("foo"), "~$foo" )
46
+ marshals_scalar("a Fixnum", 9007199254740999, "~i9007199254740999")
47
+ marshals_scalar("a Bignum", 9223372036854775806, "~i9223372036854775806")
48
+ marshals_scalar("a Very Bignum", 4256768765123454321897654321234567, "~n4256768765123454321897654321234567")
49
+ # Ruby's base64 encoding adds line feed in every 60 encoded
50
+ # characters, while Java,
51
+ # org.apache.commons.codec.binary.Base64, doesn't add any. Java
52
+ # method has option to add line feed, but every 76 characters.
53
+ # this divergence may be inevitable
54
+ if Transit::jruby?
55
+ marshals_scalar("a ByteArray", ByteArray.new(bytes), "~b#{ByteArray.new(bytes).to_base64}".gsub(/\n/, ""))
56
+ else
57
+ marshals_scalar("a ByteArray", ByteArray.new(bytes), "~b#{ByteArray.new(bytes).to_base64}")
58
+ end
59
+ marshals_scalar("an URI", Addressable::URI.parse("http://example.com/search"), "~rhttp://example.com/search")
60
+ marshals_structure("a link",
61
+ Link.new(Addressable::URI.parse("http://example.com/search"), "search", nil, "link", nil),
62
+ {"~#link" =>
63
+ {"href" => "~rhttp://example.com/search",
64
+ "rel" => "search",
65
+ "name" => nil,
66
+ "render" => "link",
67
+ "prompt" => nil}})
68
+ marshals_structure("a TaggedValue", TaggedValue.new("tag", "value"), {"~#tag" => "value"})
69
+ marshals_structure("a ratio by Rational class", Rational(1, 3), {"~#ratio" => [1, 3]})
70
+ marshals_structure("a Rational with big number", Rational(4953778853208128465, 636801457410081246), {"~#ratio" => ["~i4953778853208128465", "~i636801457410081246"]})
71
+ end
72
+
73
+ describe "custom handlers" do
74
+ it "raises when a handler provides nil as a tag" do
75
+ handler = Class.new do
76
+ def tag(_) nil end
77
+ end
78
+ writer = Writer.new(:json_verbose, io, :handlers => {Date => handler.new})
79
+ if Transit::jruby?
80
+ assert { rescuing { writer.write(Date.today) }.message =~ /Not supported/ }
81
+ else
82
+ assert { rescuing { writer.write(Date.today) }.message =~ /must provide a non-nil tag/ }
83
+ end
84
+ end
85
+
86
+ it "supports custom handlers for core types" do
87
+ handler = Class.new do
88
+ def tag(_) "s" end
89
+ def rep(s) "MYSTRING: #{s}" end
90
+ def string_rep(s) rep(s) end
91
+ end
92
+ writer = Writer.new(:json_verbose, io, :handlers => {String => handler.new})
93
+ writer.write("this")
94
+ assert { JSON.parse(io.string).values.first == "MYSTRING: this" }
95
+ end
96
+
97
+ it "supports custom handlers for custom types" do
98
+ handler = Class.new do
99
+ def tag(_) "person" end
100
+ def rep(s) {:first_name => s.first_name} end
101
+ def string_rep(s) s.first_name end
102
+ end
103
+ writer = Writer.new(:json_verbose, io, :handlers => {Person => handler.new})
104
+ writer.write(Person.new("Russ"))
105
+ assert { JSON.parse(io.string) == {"~#person" => { "~:first_name" => "Russ" } } }
106
+ end
107
+
108
+ it "supports verbose handlers" do
109
+ phone_class = Class.new do
110
+ attr_reader :p
111
+ def initialize(p)
112
+ @p = p
113
+ end
114
+ end
115
+ handler = Class.new do
116
+ def tag(_) "phone" end
117
+ def rep(v) v.p end
118
+ def string_rep(v) v.p.to_s end
119
+ def verbose_handler
120
+ Class.new do
121
+ def tag(_) "phone" end
122
+ def rep(v) "PHONE: #{v.p}" end
123
+ def string_rep(v) rep(v) end
124
+ end.new
125
+ end
126
+ end
127
+
128
+ writer = Writer.new(:json, io, :handlers => {phone_class => handler.new})
129
+ writer.write(phone_class.new(123456789))
130
+ assert { JSON.parse(io.string) == ["~#phone", 123456789] }
131
+
132
+ io.rewind
133
+
134
+ writer = Writer.new(:json_verbose, io, :handlers => {phone_class => handler.new})
135
+ writer.write(phone_class.new(123456789))
136
+ assert { JSON.parse(io.string) == {"~#phone" => "PHONE: 123456789"} }
137
+ end
138
+ end
139
+
140
+ describe "formats" do
141
+ describe "JSON" do
142
+ let(:writer) { Writer.new(:json, io) }
143
+
144
+ it "writes a map as an array prefixed with '^ '" do
145
+ writer.write({:a => :b, 3 => 4})
146
+ assert { JSON.parse(io.string) == ["^ ", "~:a", "~:b", "~i3", 4] }
147
+ end
148
+
149
+ it "writes a single-char tagged-value as a string" do
150
+ writer.write([TaggedValue.new("a","bc")])
151
+ assert { JSON.parse(io.string) == ["~abc"] }
152
+ end
153
+
154
+ it "writes a multi-char tagged-value as a 2-element array" do
155
+ writer.write(TaggedValue.new("abc","def"))
156
+ assert { JSON.parse(io.string) == ["~#abc", "def"] }
157
+ end
158
+
159
+ it "writes a Date as an encoded hash with ms" do
160
+ writer.write([Date.new(2014,1,2)])
161
+ assert { JSON.parse(io.string) == ["~m1388620800000"] }
162
+ end
163
+
164
+ it "writes a Time as an encoded hash with ms" do
165
+ writer.write([(Time.at(1388631845) + 0.678)])
166
+ assert { JSON.parse(io.string) == ["~m1388631845678"] }
167
+ end
168
+
169
+ it "writes a DateTime as an encoded hash with ms" do
170
+ writer.write([(Time.at(1388631845) + 0.678).to_datetime])
171
+ assert { JSON.parse(io.string) == ["~m1388631845678"] }
172
+ end
173
+
174
+ it "writes a quote as a tagged array" do
175
+ writer.write("this")
176
+ assert { JSON.parse(io.string) == ["~#'", "this"] }
177
+ end
178
+ end
179
+
180
+ describe "JSON_VERBOSE" do
181
+ let(:writer) { Writer.new(:json_verbose, io) }
182
+
183
+ it "does not use the cache" do
184
+ writer.write([{"this" => "that"}, {"this" => "the other"}])
185
+ assert { JSON.parse(io.string) == [{"this" => "that"}, {"this" => "the other"}] }
186
+ end
187
+
188
+ it "writes a single-char tagged-value as a string" do
189
+ writer.write([TaggedValue.new("a","bc")])
190
+ assert { JSON.parse(io.string) == ["~abc"] }
191
+ end
192
+
193
+ it "writes a multi-char tagged-value as a map" do
194
+ writer.write(TaggedValue.new("abc","def"))
195
+ assert { JSON.parse(io.string) == {"~#abc" => "def"} }
196
+ end
197
+
198
+ it "writes a Date as an encoded human-readable strings" do
199
+ writer.write([Date.new(2014,1,2)])
200
+ assert { JSON.parse(io.string) == ["~t2014-01-02T00:00:00.000Z"] }
201
+ end
202
+
203
+ it "writes a Time as an encoded human-readable strings" do
204
+ writer.write([(Time.at(1388631845) + 0.678)])
205
+ assert { JSON.parse(io.string) == ["~t2014-01-02T03:04:05.678Z"] }
206
+ end
207
+
208
+ it "writes a DateTime as an encoded human-readable strings" do
209
+ writer.write([(Time.at(1388631845) + 0.678).to_datetime])
210
+ assert { JSON.parse(io.string) == ["~t2014-01-02T03:04:05.678Z"] }
211
+ end
212
+
213
+ it "writes a quote as a tagged map" do
214
+ writer.write("this")
215
+ assert { JSON.parse(io.string) == {"~#'" => "this"} }
216
+ end
217
+ end
218
+
219
+ # JRuby skips these 3 examples since they use raw massage pack
220
+ # api. Also, JRuby doesn't hava good counterpart.
221
+ describe "MESSAGE_PACK", :unless => Transit::jruby? do
222
+ let(:writer) { Writer.new(:msgpack, io) }
223
+
224
+ it "writes a single-char tagged-value as a 2-element array" do
225
+ writer.write(TaggedValue.new("a","bc"))
226
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#'", "~abc"] }
227
+ end
228
+
229
+ it "writes a multi-char tagged-value as a 2-element array" do
230
+ writer.write(TaggedValue.new("abc","def"))
231
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#abc", "def"] }
232
+ end
233
+
234
+ it "writes a top-level scalar as a quote-tagged value" do
235
+ writer.write("this")
236
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#'", "this"] }
237
+ end
238
+ end
239
+
240
+ describe "ints" do
241
+ it "encodes ints <= max signed 64 bit with 'i'" do
242
+ 1.upto(5).to_a.reverse.each do |n|
243
+ io.rewind
244
+ writer.write([(2**63) - n])
245
+ assert { JSON.parse(io.string).first[1] == "i" }
246
+ end
247
+ end
248
+
249
+ it "encodes ints > max signed 64 bit with 'n'" do
250
+ 0.upto(4).each do |n|
251
+ io.rewind
252
+ writer.write([(2**63) + n])
253
+ assert { JSON.parse(io.string).first[1] == "n" }
254
+ end
255
+ end
256
+ end
257
+
258
+ describe "escaped strings" do
259
+ [ESC, SUB, RES, "#{SUB} "].each do |c|
260
+ it "escapes a String starting with #{c}" do
261
+ writer.write("#{c}whatever")
262
+ assert { JSON.parse(io.string) == {"#{TAG}#{QUOTE}" => "~#{c}whatever"}}
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "edge cases" do
268
+ it 'writes correct json for TaggedValues in a map-as-array (json)' do
269
+ writer = Writer.new(:json, io)
270
+ v = {7924023966712353515692932 => TaggedValue.new("ratio", [1, 3]),
271
+ 100 => TaggedValue.new("ratio", [1, 2])}
272
+ writer.write(v)
273
+ expected = ["^ ",
274
+ "~n7924023966712353515692932", ["~#ratio", [1,3]],
275
+ "~i100",["^1", [1,2]]]
276
+ actual = io.string
277
+ assert { JSON.parse(io.string) == expected }
278
+ end
279
+
280
+ it 'writes out strings starting with `' do
281
+ v = "`~hello"
282
+ writer.write([v])
283
+ assert { JSON.parse(io.string).first == "~`~hello" }
284
+ end
285
+
286
+ it 'raises when there is no handler for the type' do
287
+ if Transit::jruby?
288
+ assert { rescuing { writer.write(Class.new.new) }.message =~ /Not supported/ }
289
+ else
290
+ assert { rescuing { writer.write(Class.new.new) }.message =~ /Can not find a Write Handler/ }
291
+ end
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end