transit-ruby 0.8.467

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,179 @@
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 round_trip(obj, type, opts={})
18
+ obj_before = obj
19
+
20
+ io = StringIO.new('', 'w+')
21
+ writer = Transit::Writer.new(type, io, :handlers => opts[:write_handlers])
22
+ writer.write(obj)
23
+
24
+ # ensure that we don't modify the object being written
25
+ assert { obj == obj_before }
26
+ reader = Transit::Reader.new(type, StringIO.new(io.string), :handlers => opts[:read_handlers])
27
+ reader.read
28
+ end
29
+
30
+ def assert_equal_times(actual,expected)
31
+ assert { Transit::DateTimeUtil.to_millis(actual) == Transit::DateTimeUtil.to_millis(expected) }
32
+ assert { actual.zone == expected.zone }
33
+ end
34
+
35
+ def round_trips(label, obj, type, opts={})
36
+ expected = opts[:expected] || obj
37
+
38
+ it "round trips #{label} at top level", :focus => !!opts[:focus], :pending => opts[:pending] do
39
+ case obj
40
+ when Date, Time, DateTime
41
+ assert_equal_times(round_trip(obj, type), expected)
42
+ else
43
+ actual = round_trip(obj, type, opts)
44
+ expect(actual).to eq(expected)
45
+ end
46
+ end
47
+
48
+ case obj
49
+ when Date, Time, DateTime
50
+ it "round trips #{label} as a map key", :focus => !!opts[:focus], :pending => opts[:pending] do
51
+ after = round_trip({obj => 0}, type)
52
+ assert_equal_times(after.keys.first, expected)
53
+ end
54
+ else
55
+ it "round trips #{label} as a map key", :focus => !!opts[:focus], :pending => opts[:pending] do
56
+ actual = round_trip({obj => 0}, type, opts)
57
+ wrapped_expected = {expected => 0}
58
+ assert { actual == wrapped_expected }
59
+ end
60
+ end
61
+
62
+ it "round trips #{label} as a map value", :focus => !!opts[:focus], :pending => opts[:pending] do
63
+ case obj
64
+ when Date, Time, DateTime
65
+ after = round_trip({:a => obj}, type)
66
+ assert_equal_times(after.values.first, expected)
67
+ else
68
+ actual = round_trip({a: obj}, type, opts)
69
+ assert { actual == {a: expected} }
70
+ end
71
+ end
72
+
73
+ it "round trips #{label} as an array value", :focus => !!opts[:focus], :pending => opts[:pending] do
74
+ case obj
75
+ when Date, Time, DateTime
76
+ after = round_trip([obj], type)
77
+ assert_equal_times(after.first, expected)
78
+ else
79
+ actual = round_trip([obj], type, opts)
80
+ assert { actual == [expected] }
81
+ end
82
+ end
83
+ end
84
+
85
+ module Transit
86
+ PhoneNumber = Struct.new(:area, :prefix, :suffix)
87
+ def PhoneNumber.parse(p)
88
+ area, prefix, suffix = p.split(".")
89
+ PhoneNumber.new(area, prefix, suffix)
90
+ end
91
+
92
+ class PhoneNumberHandler
93
+ def tag(_) "P" end
94
+ def rep(p) "#{p.area}.#{p.prefix}.#{p.suffix}" end
95
+ def string_rep(p) rep(p) end
96
+ end
97
+
98
+ class PhoneNumberReadHandler
99
+ def from_rep(v) PhoneNumber.parse(v) end
100
+ end
101
+
102
+ class PersonReadHandler
103
+ def from_rep(v)
104
+ Person.new(v[:first_name],v[:last_name],v[:birthdate])
105
+ end
106
+ end
107
+
108
+ shared_examples "round trips" do |type|
109
+ round_trips("nil", nil, type)
110
+ round_trips("a keyword", random_symbol, type)
111
+ round_trips("a string", random_string, type)
112
+ round_trips("a string starting with ~", "~#{random_string}", type)
113
+ round_trips("a string starting with ^", "^#{random_string}", type)
114
+ round_trips("a string starting with `", "`#{random_string}", type)
115
+ round_trips("true", true, type)
116
+ round_trips("false", false, type)
117
+ round_trips("a small int", 1, type)
118
+ round_trips("a big int", 123456789012345, type)
119
+ round_trips("a very big int", 123456789012345679012345678890, type)
120
+ round_trips("a float", 1234.56, type)
121
+ round_trips("a bigdec", BigDecimal.new("123.45"), type)
122
+ round_trips("an instant (DateTime local)", DateTime.new(2014,1,2,3,4,5, "-5"), type,
123
+ :expected => DateTime.new(2014,1,2, (3+5) ,4,5, "+00:00"))
124
+ round_trips("an instant (DateTime gmt)", DateTime.new(2014,1,2,3,4,5), type)
125
+ round_trips("an instant (Time local)", Time.new(2014,1,2,3,4,5, "-05:00"), type,
126
+ :expected => DateTime.new(2014,1,2, (3+5) ,4,5, "+00:00"))
127
+ round_trips("an instant (Time gmt)", Time.new(2014,1,2,3,4,5, "+00:00"), type,
128
+ :expected => DateTime.new(2014,1,2,3,4,5, "+00:00"))
129
+ round_trips("a Date", Date.new(2014,1,2), type, :expected => DateTime.new(2014,1,2))
130
+ round_trips("a uuid", UUID.new, type)
131
+ round_trips("a link", Link.new(Addressable::URI.parse("http://example.org/search"), "search"), type)
132
+ round_trips("a link", Link.new(Addressable::URI.parse("http://example.org/search"), "search", nil, "image"), type)
133
+ round_trips("a link with string uri", Link.new("http://example.org/search", "search", nil, "image"), type)
134
+ round_trips("a uri (url)", Addressable::URI.parse("http://example.com"), type)
135
+ round_trips("a uri (file)", Addressable::URI.parse("file:///path/to/file.txt"), type)
136
+ round_trips("a bytearray", ByteArray.new(random_string(50)), type)
137
+ round_trips("a Transit::Symbol", Transit::Symbol.new(random_string), type)
138
+ round_trips("a hash w/ stringable keys", {"this" => "~hash", "1" => 2}, type)
139
+ round_trips("a set", Set.new([1,2,3]), type)
140
+ round_trips("a set of sets", Set.new([Set.new([1,2]), Set.new([3,4])]), type)
141
+ round_trips("an array", [1,2,3], type)
142
+ round_trips("a char", TaggedValue.new("c", "x"), type, :expected => "x")
143
+ round_trips("a list", TaggedValue.new("list", [1,2,3]), type, :expected => [1,2,3])
144
+ round_trips("an array of maps w/ cacheable keys", [{"this" => "a"},{"this" => "b"}], type)
145
+
146
+ round_trips("edge case chars", %w[` ~ ^ #], type)
147
+
148
+ round_trips("an extension scalar", PhoneNumber.new("555","867","5309"), type,
149
+ :write_handlers => {PhoneNumber => PhoneNumberHandler.new},
150
+ :read_handlers => {"P" => PhoneNumberReadHandler.new})
151
+ round_trips("an extension struct", Person.new("First","Last",:today), type,
152
+ :write_handlers => {Person => PersonHandler.new},
153
+ :read_handlers => {"person" => PersonReadHandler.new})
154
+ round_trips("a hash with simple values", {'a' => 1, 'b' => 2, 'name' => 'russ'}, type)
155
+ round_trips("a hash with Transit::Symbols", {Transit::Symbol.new("foo") => Transit::Symbol.new("bar")}, type)
156
+ round_trips("a hash with 53 bit ints", {2**53-1 => 2**53-2}, type)
157
+ round_trips("a hash with 54 bit ints", {2**53 => 2**53+1}, type)
158
+ round_trips("a map with composite keys", {{a: :b} => {c: :d}}, type)
159
+ round_trips("a TaggedValue", TaggedValue.new("unrecognized",:value), type)
160
+ round_trips("an unrecognized hash encoding", {"~#unrecognized" => :value}, type)
161
+ round_trips("an unrecognized string encoding", "~Xunrecognized", type)
162
+
163
+ round_trips("a nested structure (map on top)", {a: [1, [{b: "~c"}]]}, type)
164
+ round_trips("a nested structure (array on top)", [37, {a: [1, [{b: "~c"}]]}], type)
165
+ round_trips("a map that looks like transit data", [{"~#set"=>[1,2,3]},{"~#set"=>[4,5,6]}], type)
166
+ end
167
+
168
+ describe "Transit using json" do
169
+ include_examples "round trips", :json
170
+ end
171
+
172
+ describe "Transit using json_verbose" do
173
+ include_examples "round trips", :json_verbose
174
+ end
175
+
176
+ describe "Transit using msgpack" do
177
+ include_examples "round trips", :msgpack
178
+ end
179
+ 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,277 @@
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
+ marshals_scalar("a ByteArray", ByteArray.new(bytes), "~b#{ByteArray.new(bytes).to_base64}")
50
+ marshals_scalar("an URI", Addressable::URI.parse("http://example.com/search"), "~rhttp://example.com/search")
51
+ marshals_structure("a link",
52
+ Link.new(Addressable::URI.parse("http://example.com/search"), "search", nil, "link", nil),
53
+ {"~#link" =>
54
+ {"href" => "~rhttp://example.com/search",
55
+ "rel" => "search",
56
+ "name" => nil,
57
+ "render" => "link",
58
+ "prompt" => nil}})
59
+ marshals_structure("a TaggedValue", TaggedValue.new("tag", "value"), {"~#tag" => "value"})
60
+ end
61
+
62
+ describe "custom handlers" do
63
+ it "raises when a handler provides nil as a tag" do
64
+ handler = Class.new do
65
+ def tag(_) nil end
66
+ end
67
+ writer = Writer.new(:json_verbose, io, :handlers => {Date => handler.new})
68
+ assert { rescuing { writer.write(Date.today) }.message =~ /must provide a non-nil tag/ }
69
+ end
70
+
71
+ it "supports custom handlers for core types" do
72
+ handler = Class.new do
73
+ def tag(_) "s" end
74
+ def rep(s) "MYSTRING: #{s}" end
75
+ def string_rep(s) rep(s) end
76
+ end
77
+ writer = Writer.new(:json_verbose, io, :handlers => {String => handler.new})
78
+ writer.write("this")
79
+ assert { JSON.parse(io.string).values.first == "MYSTRING: this" }
80
+ end
81
+
82
+ it "supports custom handlers for custom types" do
83
+ handler = Class.new do
84
+ def tag(_) "person" end
85
+ def rep(s) {:first_name => s.first_name} end
86
+ def string_rep(s) s.first_name end
87
+ end
88
+ writer = Writer.new(:json_verbose, io, :handlers => {Person => handler.new})
89
+ writer.write(Person.new("Russ"))
90
+ assert { JSON.parse(io.string) == {"~#person" => { "~:first_name" => "Russ" } } }
91
+ end
92
+
93
+ it "supports verbose handlers" do
94
+ phone_class = Class.new do
95
+ attr_reader :p
96
+ def initialize(p)
97
+ @p = p
98
+ end
99
+ end
100
+ handler = Class.new do
101
+ def tag(_) "phone" end
102
+ def rep(v) v.p end
103
+ def string_rep(v) v.p.to_s end
104
+ def verbose_handler
105
+ Class.new do
106
+ def tag(_) "phone" end
107
+ def rep(v) "PHONE: #{v.p}" end
108
+ def string_rep(v) rep(v) end
109
+ end.new
110
+ end
111
+ end
112
+
113
+ writer = Writer.new(:json, io, :handlers => {phone_class => handler.new})
114
+ writer.write(phone_class.new(123456789))
115
+ assert { JSON.parse(io.string) == ["~#phone", 123456789] }
116
+
117
+ io.rewind
118
+
119
+ writer = Writer.new(:json_verbose, io, :handlers => {phone_class => handler.new})
120
+ writer.write(phone_class.new(123456789))
121
+ assert { JSON.parse(io.string) == {"~#phone" => "PHONE: 123456789"} }
122
+ end
123
+ end
124
+
125
+ describe "formats" do
126
+ describe "JSON" do
127
+ let(:writer) { Writer.new(:json, io) }
128
+
129
+ it "writes a map as an array prefixed with '^ '" do
130
+ writer.write({:a => :b, 3 => 4})
131
+ assert { JSON.parse(io.string) == ["^ ", "~:a", "~:b", "~i3", 4] }
132
+ end
133
+
134
+ it "writes a single-char tagged-value as a string" do
135
+ writer.write([TaggedValue.new("a","bc")])
136
+ assert { JSON.parse(io.string) == ["~abc"] }
137
+ end
138
+
139
+ it "writes a multi-char tagged-value as a 2-element array" do
140
+ writer.write(TaggedValue.new("abc","def"))
141
+ assert { JSON.parse(io.string) == ["~#abc", "def"] }
142
+ end
143
+
144
+ it "writes a Date as an encoded hash with ms" do
145
+ writer.write([Date.new(2014,1,2)])
146
+ assert { JSON.parse(io.string) == ["~m1388620800000"] }
147
+ end
148
+
149
+ it "writes a Time as an encoded hash with ms" do
150
+ writer.write([(Time.at(1388631845) + 0.678)])
151
+ assert { JSON.parse(io.string) == ["~m1388631845678"] }
152
+ end
153
+
154
+ it "writes a DateTime as an encoded hash with ms" do
155
+ writer.write([(Time.at(1388631845) + 0.678).to_datetime])
156
+ assert { JSON.parse(io.string) == ["~m1388631845678"] }
157
+ end
158
+
159
+ it "writes a quote as a tagged array" do
160
+ writer.write("this")
161
+ assert { JSON.parse(io.string) == ["~#'", "this"] }
162
+ end
163
+ end
164
+
165
+ describe "JSON_VERBOSE" do
166
+ let(:writer) { Writer.new(:json_verbose, io) }
167
+
168
+ it "does not use the cache" do
169
+ writer.write([{"this" => "that"}, {"this" => "the other"}])
170
+ assert { JSON.parse(io.string) == [{"this" => "that"}, {"this" => "the other"}] }
171
+ end
172
+
173
+ it "writes a single-char tagged-value as a string" do
174
+ writer.write([TaggedValue.new("a","bc")])
175
+ assert { JSON.parse(io.string) == ["~abc"] }
176
+ end
177
+
178
+ it "writes a multi-char tagged-value as a map" do
179
+ writer.write(TaggedValue.new("abc","def"))
180
+ assert { JSON.parse(io.string) == {"~#abc" => "def"} }
181
+ end
182
+
183
+ it "writes a Date as an encoded human-readable strings" do
184
+ writer.write([Date.new(2014,1,2)])
185
+ assert { JSON.parse(io.string) == ["~t2014-01-02T00:00:00.000Z"] }
186
+ end
187
+
188
+ it "writes a Time as an encoded human-readable strings" do
189
+ writer.write([(Time.at(1388631845) + 0.678)])
190
+ assert { JSON.parse(io.string) == ["~t2014-01-02T03:04:05.678Z"] }
191
+ end
192
+
193
+ it "writes a DateTime as an encoded human-readable strings" do
194
+ writer.write([(Time.at(1388631845) + 0.678).to_datetime])
195
+ assert { JSON.parse(io.string) == ["~t2014-01-02T03:04:05.678Z"] }
196
+ end
197
+
198
+ it "writes a quote as a tagged map" do
199
+ writer.write("this")
200
+ assert { JSON.parse(io.string) == {"~#'" => "this"} }
201
+ end
202
+ end
203
+
204
+ describe "MESSAGE_PACK" do
205
+ let(:writer) { Writer.new(:msgpack, io) }
206
+
207
+ it "writes a single-char tagged-value as a 2-element array" do
208
+ writer.write(TaggedValue.new("a","bc"))
209
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#'", "~abc"] }
210
+ end
211
+
212
+ it "writes a multi-char tagged-value as a 2-element array" do
213
+ writer.write(TaggedValue.new("abc","def"))
214
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#abc", "def"] }
215
+ end
216
+
217
+ it "writes a top-level scalar as a quote-tagged value" do
218
+ writer.write("this")
219
+ assert { MessagePack::Unpacker.new(StringIO.new(io.string)).read == ["~#'", "this"] }
220
+ end
221
+ end
222
+
223
+ describe "ints" do
224
+ it "encodes ints <= max signed 64 bit with 'i'" do
225
+ 1.upto(5).to_a.reverse.each do |n|
226
+ io.rewind
227
+ writer.write([(2**63) - n])
228
+ assert { JSON.parse(io.string).first[1] == "i" }
229
+ end
230
+ end
231
+
232
+ it "encodes ints > max signed 64 bit with 'n'" do
233
+ 0.upto(4).each do |n|
234
+ io.rewind
235
+ writer.write([(2**63) + n])
236
+ assert { JSON.parse(io.string).first[1] == "n" }
237
+ end
238
+ end
239
+ end
240
+
241
+ describe "escaped strings" do
242
+ [ESC, SUB, RES, "#{SUB} "].each do |c|
243
+ it "escapes a String starting with #{c}" do
244
+ writer.write("#{c}whatever")
245
+ assert { JSON.parse(io.string) == {"#{TAG}#{QUOTE}" => "~#{c}whatever"}}
246
+ end
247
+ end
248
+ end
249
+
250
+ describe "edge cases" do
251
+ it 'writes correct json for TaggedValues in a map-as-array (json)' do
252
+ writer = Writer.new(:json, io)
253
+ v = {7924023966712353515692932 => TaggedValue.new("ratio", [1, 3]),
254
+ 100 => TaggedValue.new("ratio", [1, 2])}
255
+ writer.write(v)
256
+ expected = ["^ ",
257
+ "~n7924023966712353515692932", ["~#ratio", [1,3]],
258
+ "~i100",["^1", [1,2]]]
259
+ actual = io.string
260
+ assert { JSON.parse(io.string) == expected }
261
+ end
262
+
263
+ it 'writes out strings starting with `' do
264
+ v = "`~hello"
265
+ writer.write([v])
266
+ assert { JSON.parse(io.string).first == "~`~hello" }
267
+ end
268
+
269
+ it 'raises when there is no handler for the type' do
270
+ type = Class.new
271
+ obj = type.new
272
+ assert { rescuing { writer.write(obj) }.message =~ /Can not find a Write Handler/ }
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end