xnlogic-transit-ruby 0.8.572-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,170 @@
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 Reader do
19
+ shared_examples "read without a block" do |type|
20
+ it "reads a single top-level #{type} element" do
21
+ input = {:this => [1,2,3,{:that => "the other"}]}
22
+
23
+ io = StringIO.new('', 'w+')
24
+ writer = Transit::Writer.new(type, io)
25
+ writer.write(input)
26
+
27
+ reader = Transit::Reader.new(type, StringIO.new(io.string))
28
+ assert { reader.read == input }
29
+ end
30
+ end
31
+
32
+ describe "reading without a block" do
33
+ include_examples "read without a block", :json
34
+ include_examples "read without a block", :json_verbose
35
+ include_examples "read without a block", :msgpack
36
+ end
37
+
38
+ shared_examples "read with a block" do |type|
39
+ it "reads multiple top-level #{type} elements from a single IO" do
40
+ inputs = ["abc",
41
+ 123456789012345678901234567890,
42
+ [:this, :that],
43
+ {:this => [1,2,3,{:that => "the other"}]}]
44
+ outputs = []
45
+
46
+ io = StringIO.new('', 'w+')
47
+ writer = Transit::Writer.new(type, io)
48
+ inputs.each {|i| writer.write(i)}
49
+ reader = Transit::Reader.new(type, StringIO.new(io.string))
50
+ if Transit::jruby?
51
+ # Ignore expected EOFException raised after the StringIO is exhausted
52
+ reader.read {|val| outputs << val} rescue nil
53
+ else
54
+ reader.read {|val| outputs << val}
55
+ end
56
+
57
+ assert { outputs == inputs }
58
+ end
59
+ end
60
+
61
+ describe "reading with a block" do
62
+ include_examples "read with a block", :json
63
+ include_examples "read with a block", :json_verbose
64
+ include_examples "read with a block", :msgpack
65
+ end
66
+
67
+ describe 'handler registration' do
68
+ describe 'overrides' do
69
+ describe 'ground types' do
70
+ Decoder::GROUND_TAGS.each do |ground|
71
+ it "prevents override of #{ground} handler" do
72
+ assert {
73
+ rescuing {
74
+ Reader.new(:json, StringIO.new, :handlers => {ground => Object.new})
75
+ }.message =~ /ground types/ }
76
+ end
77
+ end
78
+ end
79
+
80
+ it 'supports override of default string handlers' do
81
+ io = StringIO.new("[\"~rhttp://foo.com\"]","r+")
82
+ reader = Reader.new(:json, io, :handlers => {"r" => Class.new { def from_rep(v) "DECODED: #{v}" end}.new})
83
+ assert { reader.read == ["DECODED: http://foo.com"] }
84
+ end
85
+
86
+ it 'supports override of default hash handlers' do
87
+ my_uuid_class = Class.new(String)
88
+ my_uuid = my_uuid_class.new(UUID.new.to_s)
89
+ io = StringIO.new({"~#u" => my_uuid.to_s}.to_json)
90
+ reader = Reader.new(:json, io, :handlers => {"u" => Class.new { define_method(:from_rep) {|v| my_uuid_class.new(v)}}.new})
91
+ assert { reader.read == my_uuid }
92
+ end
93
+
94
+ it 'supports override of the default handler' do
95
+ io = StringIO.new("~Xabc".to_json)
96
+ reader = Reader.new(:json, io, :default_handler => Class.new { def from_rep(tag,val) raise "Unacceptable: #{s}" end}.new)
97
+ assert { rescuing {reader.read }.message =~ /Unacceptable/ }
98
+ end
99
+ end
100
+
101
+ describe 'extensions' do
102
+ it 'supports string-based extensions' do
103
+ io = StringIO.new("~D2014-03-15".to_json)
104
+ reader = Reader.new(:json, io, :handlers => {"D" => Class.new { def from_rep(v) Date.parse(v) end}.new})
105
+ assert { reader.read == Date.new(2014,3,15) }
106
+ end
107
+
108
+ it 'supports hash based extensions' do
109
+ io = StringIO.new({"~#Times2" => 44}.to_json)
110
+ reader = Reader.new(:json, io, :handlers => {"Times2" => Class.new { def from_rep(v) v * 2 end}.new})
111
+ assert { reader.read == 88 }
112
+ end
113
+
114
+ it 'supports hash based extensions that return nil' do
115
+ io = StringIO.new({"~#Nil" => :anything}.to_json)
116
+ reader = Reader.new(:json, io, :handlers => {"Nil" => Class.new { def from_rep(_) nil end}.new})
117
+ assert { reader.read == nil }
118
+ end
119
+
120
+ it 'supports hash based extensions that return false' do
121
+ io = StringIO.new({"~#False" => :anything}.to_json)
122
+ reader = Reader.new(:json, io, :handlers => {"False" => Class.new { def from_rep(_) false end}.new})
123
+ assert { reader.read == false }
124
+ end
125
+
126
+ it 'supports complex hash values' do
127
+ io = StringIO.new([
128
+ {"~#person"=>
129
+ {"~:first_name" => "Transit","~:last_name" => "Ruby","~:birthdate" => "~D2014-01-02"}},
130
+ {"^0"=>
131
+ {"^1" => "Transit","^2" => "Ruby","^3" => "~D2014-01-03"}}].to_json)
132
+
133
+ person_handler = Class.new do
134
+ def from_rep(v)
135
+ Person.new(v[:first_name],v[:last_name],v[:birthdate])
136
+ end
137
+ end
138
+ date_handler = Class.new do
139
+ def from_rep(v) Date.parse(v) end
140
+ end
141
+ reader = Reader.new(:json, io,
142
+ :handlers => {
143
+ "person" => person_handler.new,
144
+ "D" => date_handler.new})
145
+ expected = [Person.new("Transit", "Ruby", Date.new(2014,1,2)),
146
+ Person.new("Transit", "Ruby", Date.new(2014,1,3))]
147
+ assert { reader.read == expected }
148
+ end
149
+ end
150
+
151
+ describe 'Dates/Times' do
152
+ it "delivers a UTC DateTime for a non-UTC date string" do
153
+ io = StringIO.new(["~t2014-04-14T12:20:50.152-05:00"].to_json)
154
+ reader = Reader.new(:json, io)
155
+ expect(Transit::DateTimeUtil.to_millis(reader.read.first)).to eq(Transit::DateTimeUtil.to_millis(DateTime.new(2014,4,14,17,20,50.152,"Z")))
156
+ end
157
+ end
158
+
159
+ describe 'edge cases found in generative testing' do
160
+ it 'caches nested structures correctly' do
161
+ io = StringIO.new(["~#cmap",[["~#point",["~n10","~n11"]],"~:foobar",["^1",["~n10","~n13"]],"^2"]].to_json)
162
+ reader = Reader.new(:json, io)
163
+ expected = {TaggedValue.new("point", [10,11]) => :foobar,
164
+ TaggedValue.new("point", [10,13]) => :foobar}
165
+ assert { reader.read == expected }
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -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,144 @@
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
+
137
+ describe ByteArray do
138
+ it 'uses the default_external encoding for to_s' do
139
+ src = ByteArray.new("foo".force_encoding("ascii")).to_base64
140
+ target = ByteArray.from_base64(src)
141
+ assert { target.to_s.encoding == Encoding.default_external }
142
+ end
143
+ end
144
+ end