lutaml-model 0.3.24 → 0.3.25
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +35 -16
- data/README.adoc +274 -28
- data/lib/lutaml/model/attribute.rb +18 -8
- data/lib/lutaml/model/error/type_error.rb +9 -0
- data/lib/lutaml/model/error/unknown_type_error.rb +9 -0
- data/lib/lutaml/model/error/validation_error.rb +0 -1
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/serialize.rb +6 -1
- data/lib/lutaml/model/type/boolean.rb +38 -0
- data/lib/lutaml/model/type/date.rb +35 -0
- data/lib/lutaml/model/type/date_time.rb +32 -4
- data/lib/lutaml/model/type/decimal.rb +42 -0
- data/lib/lutaml/model/type/float.rb +37 -0
- data/lib/lutaml/model/type/hash.rb +62 -0
- data/lib/lutaml/model/type/integer.rb +41 -0
- data/lib/lutaml/model/type/string.rb +49 -0
- data/lib/lutaml/model/type/time.rb +49 -0
- data/lib/lutaml/model/type/time_without_date.rb +37 -5
- data/lib/lutaml/model/type/value.rb +52 -0
- data/lib/lutaml/model/type.rb +50 -114
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/builder/nokogiri.rb +5 -2
- data/lib/lutaml/model/xml_adapter/ox_adapter.rb +2 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +0 -2
- data/lutaml-model.gemspec +1 -1
- data/spec/address_spec.rb +170 -0
- data/spec/fixtures/address.rb +33 -0
- data/spec/fixtures/person.rb +73 -0
- data/spec/fixtures/sample_model.rb +40 -0
- data/spec/fixtures/vase.rb +38 -0
- data/spec/fixtures/xml/special_char.xml +13 -0
- data/spec/lutaml/model/attribute_spec.rb +112 -0
- data/spec/lutaml/model/collection_spec.rb +299 -0
- data/spec/lutaml/model/comparable_model_spec.rb +106 -0
- data/spec/lutaml/model/custom_model_spec.rb +410 -0
- data/spec/lutaml/model/custom_serialization_spec.rb +170 -0
- data/spec/lutaml/model/defaults_spec.rb +221 -0
- data/spec/lutaml/model/delegation_spec.rb +340 -0
- data/spec/lutaml/model/inheritance_spec.rb +92 -0
- data/spec/lutaml/model/json_adapter_spec.rb +37 -0
- data/spec/lutaml/model/key_value_mapping_spec.rb +86 -0
- data/spec/lutaml/model/map_content_spec.rb +118 -0
- data/spec/lutaml/model/mixed_content_spec.rb +625 -0
- data/spec/lutaml/model/namespace_spec.rb +57 -0
- data/spec/lutaml/model/ordered_content_spec.rb +83 -0
- data/spec/lutaml/model/render_nil_spec.rb +138 -0
- data/spec/lutaml/model/schema/json_schema_spec.rb +79 -0
- data/spec/lutaml/model/schema/relaxng_schema_spec.rb +60 -0
- data/spec/lutaml/model/schema/xsd_schema_spec.rb +55 -0
- data/spec/lutaml/model/schema/yaml_schema_spec.rb +47 -0
- data/spec/lutaml/model/serializable_spec.rb +297 -0
- data/spec/lutaml/model/serializable_validation_spec.rb +85 -0
- data/spec/lutaml/model/simple_model_spec.rb +314 -0
- data/spec/lutaml/model/toml_adapter_spec.rb +39 -0
- data/spec/lutaml/model/type/boolean_spec.rb +54 -0
- data/spec/lutaml/model/type/date_spec.rb +118 -0
- data/spec/lutaml/model/type/date_time_spec.rb +127 -0
- data/spec/lutaml/model/type/decimal_spec.rb +125 -0
- data/spec/lutaml/model/type/float_spec.rb +191 -0
- data/spec/lutaml/model/type/hash_spec.rb +63 -0
- data/spec/lutaml/model/type/integer_spec.rb +145 -0
- data/spec/lutaml/model/type/string_spec.rb +150 -0
- data/spec/lutaml/model/type/time_spec.rb +142 -0
- data/spec/lutaml/model/type/time_without_date_spec.rb +125 -0
- data/spec/lutaml/model/type_spec.rb +276 -0
- data/spec/lutaml/model/utils_spec.rb +79 -0
- data/spec/lutaml/model/validation_spec.rb +83 -0
- data/spec/lutaml/model/with_child_mapping_spec.rb +174 -0
- data/spec/lutaml/model/xml_adapter/nokogiri_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/oga_adapter_spec.rb +56 -0
- data/spec/lutaml/model/xml_adapter/ox_adapter_spec.rb +61 -0
- data/spec/lutaml/model/xml_adapter/xml_namespace_spec.rb +251 -0
- data/spec/lutaml/model/xml_adapter_spec.rb +178 -0
- data/spec/lutaml/model/xml_mapping_spec.rb +863 -0
- data/spec/lutaml/model/yaml_adapter_spec.rb +30 -0
- data/spec/lutaml/model_spec.rb +1 -0
- data/spec/person_spec.rb +161 -0
- data/spec/spec_helper.rb +33 -0
- metadata +66 -2
@@ -0,0 +1,63 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Hash do
|
4
|
+
describe ".cast" do
|
5
|
+
it "returns nil for nil input" do
|
6
|
+
expect(described_class.cast(nil)).to be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns text content from hash with only text key" do
|
10
|
+
hash = { "text" => "content" }
|
11
|
+
expect(described_class.cast(hash)).to eq "content"
|
12
|
+
end
|
13
|
+
|
14
|
+
it "normalizes MappingHash to regular Hash" do
|
15
|
+
mapping_hash = Lutaml::Model::MappingHash.new
|
16
|
+
mapping_hash["key"] = "value"
|
17
|
+
expect(described_class.cast(mapping_hash)).to eq({ "key" => "value" })
|
18
|
+
end
|
19
|
+
|
20
|
+
it "filters out text keys from nested hashes" do
|
21
|
+
hash = {
|
22
|
+
"key1" => {
|
23
|
+
"text" => "content1",
|
24
|
+
"other" => "value1",
|
25
|
+
},
|
26
|
+
"key2" => {
|
27
|
+
"text" => "content2",
|
28
|
+
"other" => "value2",
|
29
|
+
},
|
30
|
+
}
|
31
|
+
expected = {
|
32
|
+
"key1" => { "other" => "value1" },
|
33
|
+
"key2" => { "other" => "value2" },
|
34
|
+
}
|
35
|
+
expect(described_class.cast(hash)).to eq expected
|
36
|
+
end
|
37
|
+
|
38
|
+
it "preserves non-hash values" do
|
39
|
+
input = {
|
40
|
+
"string" => "value",
|
41
|
+
"number" => 42,
|
42
|
+
"array" => [1, 2, 3],
|
43
|
+
}
|
44
|
+
expect(described_class.cast(input)).to eq input
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".serialize" do
|
49
|
+
it "returns nil for nil input" do
|
50
|
+
expect(described_class.serialize(nil)).to be_nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "converts hash to hash" do
|
54
|
+
hash = { "key" => "value" }
|
55
|
+
expect(described_class.serialize(hash)).to eq hash
|
56
|
+
end
|
57
|
+
|
58
|
+
it "converts arbitrary object responding to to_h" do
|
59
|
+
obj = double(to_h: { "key" => "value" })
|
60
|
+
expect(described_class.serialize(obj)).to eq({ "key" => "value" })
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Integer do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with integer" do
|
14
|
+
let(:value) { 123 }
|
15
|
+
|
16
|
+
it { is_expected.to eq(123) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with integer string" do
|
20
|
+
let(:value) { "123" }
|
21
|
+
|
22
|
+
it { is_expected.to eq(123) }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with negative integer string" do
|
26
|
+
let(:value) { "-123" }
|
27
|
+
|
28
|
+
it { is_expected.to eq(-123) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with float" do
|
32
|
+
let(:value) { 123.45 }
|
33
|
+
|
34
|
+
it { is_expected.to eq(123) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with float string" do
|
38
|
+
let(:value) { "123.45" }
|
39
|
+
|
40
|
+
it { is_expected.to eq(123) }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with exponential notation" do
|
44
|
+
let(:value) { "1.23e2" }
|
45
|
+
|
46
|
+
it { is_expected.to eq(123) }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with negative exponential notation" do
|
50
|
+
let(:value) { "-1.23e2" }
|
51
|
+
|
52
|
+
it { is_expected.to eq(-123) }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with string containing a leading zero represents octal" do
|
56
|
+
let(:value) { "0123" }
|
57
|
+
|
58
|
+
it { is_expected.to eq(83) }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with plus sign" do
|
62
|
+
let(:value) { "+123" }
|
63
|
+
|
64
|
+
it { is_expected.to eq(123) }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with whitespace" do
|
68
|
+
let(:value) { " 123 " }
|
69
|
+
|
70
|
+
it { is_expected.to eq(123) }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with boolean true" do
|
74
|
+
let(:value) { true }
|
75
|
+
|
76
|
+
it { is_expected.to eq(1) }
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with boolean false" do
|
80
|
+
let(:value) { false }
|
81
|
+
|
82
|
+
it { is_expected.to eq(0) }
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with invalid string" do
|
86
|
+
let(:value) { "not an integer" }
|
87
|
+
|
88
|
+
it { is_expected.to be_nil }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with very large integer" do
|
92
|
+
let(:max_value) { ((2**((0.size * 8) - 2)) - 1) }
|
93
|
+
let(:value) { max_value.to_s }
|
94
|
+
|
95
|
+
xit { is_expected.to eq(max_value) }
|
96
|
+
end
|
97
|
+
|
98
|
+
context "with very small integer" do
|
99
|
+
let(:min_value) { -(2**((0.size * 8) - 2)) }
|
100
|
+
let(:value) { min_value.to_s }
|
101
|
+
|
102
|
+
it { is_expected.to eq(min_value) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".serialize" do
|
107
|
+
subject(:serialize) { described_class.serialize(value) }
|
108
|
+
|
109
|
+
context "with nil value" do
|
110
|
+
let(:value) { nil }
|
111
|
+
|
112
|
+
it { is_expected.to be_nil }
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with positive integer" do
|
116
|
+
let(:value) { 123 }
|
117
|
+
|
118
|
+
it { is_expected.to eq(123) }
|
119
|
+
end
|
120
|
+
|
121
|
+
context "with negative integer" do
|
122
|
+
let(:value) { -123 }
|
123
|
+
|
124
|
+
it { is_expected.to eq(-123) }
|
125
|
+
end
|
126
|
+
|
127
|
+
context "with zero" do
|
128
|
+
let(:value) { 0 }
|
129
|
+
|
130
|
+
it { is_expected.to eq(0) }
|
131
|
+
end
|
132
|
+
|
133
|
+
context "with maximum integer" do
|
134
|
+
let(:value) { 9223372036854775807 }
|
135
|
+
|
136
|
+
it { is_expected.to eq(9223372036854775807) }
|
137
|
+
end
|
138
|
+
|
139
|
+
context "with minimum integer" do
|
140
|
+
let(:value) { -9223372036854775808 }
|
141
|
+
|
142
|
+
it { is_expected.to eq(-9223372036854775808) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::String do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with string value" do
|
14
|
+
let(:value) { "test" }
|
15
|
+
|
16
|
+
it { is_expected.to eq("test") }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with symbol" do
|
20
|
+
let(:value) { :symbol }
|
21
|
+
|
22
|
+
it { is_expected.to eq("symbol") }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with integer" do
|
26
|
+
let(:value) { 123 }
|
27
|
+
|
28
|
+
it { is_expected.to eq("123") }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with float" do
|
32
|
+
let(:value) { 123.45 }
|
33
|
+
|
34
|
+
it { is_expected.to eq("123.45") }
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with true" do
|
38
|
+
let(:value) { true }
|
39
|
+
|
40
|
+
it { is_expected.to eq("true") }
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with false" do
|
44
|
+
let(:value) { false }
|
45
|
+
|
46
|
+
it { is_expected.to eq("false") }
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with Date object" do
|
50
|
+
let(:value) { Date.new(2024, 1, 1) }
|
51
|
+
|
52
|
+
it { is_expected.to eq("2024-01-01") }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "with Time object" do
|
56
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+00:00") }
|
57
|
+
|
58
|
+
it { is_expected.to match(/\A2024-01-01 12:00:00/) }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with array" do
|
62
|
+
let(:value) { [1, 2, 3] }
|
63
|
+
|
64
|
+
it { is_expected.to eq("[1, 2, 3]") }
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with hash" do
|
68
|
+
let(:value) { { a: 1, b: 2 } }
|
69
|
+
let(:expected_value) do
|
70
|
+
if RUBY_VERSION < "3.4.0"
|
71
|
+
"{:a=>1, :b=>2}"
|
72
|
+
else
|
73
|
+
"{a: 1, b: 2}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it { is_expected.to eq(expected_value) }
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with object responding to to_s" do
|
81
|
+
let(:value) do
|
82
|
+
Class.new do
|
83
|
+
def to_s
|
84
|
+
"custom string"
|
85
|
+
end
|
86
|
+
end.new
|
87
|
+
end
|
88
|
+
|
89
|
+
it { is_expected.to eq("custom string") }
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with empty string" do
|
93
|
+
let(:value) { "" }
|
94
|
+
|
95
|
+
it { is_expected.to eq("") }
|
96
|
+
end
|
97
|
+
|
98
|
+
context "with whitespace string" do
|
99
|
+
let(:value) { " \t\n " }
|
100
|
+
|
101
|
+
it { is_expected.to eq(" \t\n ") }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe ".serialize" do
|
106
|
+
subject(:serialize) { described_class.serialize(value) }
|
107
|
+
|
108
|
+
context "with nil value" do
|
109
|
+
let(:value) { nil }
|
110
|
+
|
111
|
+
it { is_expected.to be_nil }
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with string value" do
|
115
|
+
let(:value) { "test" }
|
116
|
+
|
117
|
+
it { is_expected.to eq("test") }
|
118
|
+
end
|
119
|
+
|
120
|
+
context "with empty string" do
|
121
|
+
let(:value) { "" }
|
122
|
+
|
123
|
+
it { is_expected.to eq("") }
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with whitespace string" do
|
127
|
+
let(:value) { " \t\n " }
|
128
|
+
|
129
|
+
it { is_expected.to eq(" \t\n ") }
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with string containing special characters" do
|
133
|
+
let(:value) { "test\u0000test" }
|
134
|
+
|
135
|
+
it { is_expected.to eq("test\u0000test") }
|
136
|
+
end
|
137
|
+
|
138
|
+
context "with unicode characters" do
|
139
|
+
let(:value) { "こんにちは" }
|
140
|
+
|
141
|
+
it { is_expected.to eq("こんにちは") }
|
142
|
+
end
|
143
|
+
|
144
|
+
context "with emoji" do
|
145
|
+
let(:value) { "Hello 👋" }
|
146
|
+
|
147
|
+
it { is_expected.to eq("Hello 👋") }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::Time do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with valid Time string" do
|
14
|
+
let(:value) { "2024-01-01T12:00:00Z" }
|
15
|
+
|
16
|
+
it "parses with UTC offset" do
|
17
|
+
expect(cast.utc_offset).to eq(0)
|
18
|
+
expect(cast.strftime("%:z")).to eq("+00:00")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with positive timezone offset" do
|
23
|
+
let(:value) { "2024-01-01T12:00:00+08:00" }
|
24
|
+
|
25
|
+
it "retains the positive offset" do
|
26
|
+
expect(cast.utc_offset).to eq(8 * 3600)
|
27
|
+
expect(cast.strftime("%:z")).to eq("+08:00")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with negative timezone offset" do
|
32
|
+
let(:value) { "2024-01-01T12:00:00-05:00" }
|
33
|
+
|
34
|
+
it "retains the negative offset" do
|
35
|
+
expect(cast.utc_offset).to eq(-5 * 3600)
|
36
|
+
expect(cast.strftime("%:z")).to eq("-05:00")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "with fractional offset" do
|
41
|
+
let(:value) { "2024-01-01T12:00:00+05:30" }
|
42
|
+
|
43
|
+
it "retains the fractional offset" do
|
44
|
+
expect(cast.utc_offset).to eq((5 * 3600) + (30 * 60))
|
45
|
+
expect(cast.strftime("%:z")).to eq("+05:30")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with DateTime object" do
|
50
|
+
let(:value) { DateTime.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
51
|
+
|
52
|
+
it "preserves the offset" do
|
53
|
+
expect(cast.utc_offset).to eq(8 * 3600)
|
54
|
+
expect(cast.strftime("%:z")).to eq("+08:00")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with Time object" do
|
59
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
60
|
+
|
61
|
+
it "preserves the offset" do
|
62
|
+
expect(cast.utc_offset).to eq(8 * 3600)
|
63
|
+
expect(cast.strftime("%:z")).to eq("+08:00")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with invalid Time string" do
|
68
|
+
let(:value) { "not a time" }
|
69
|
+
|
70
|
+
it { is_expected.to be_nil }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with microsecond precision" do
|
74
|
+
let(:value) { "2024-01-01T12:00:00.123456+08:00" }
|
75
|
+
|
76
|
+
it "retains microsecond precision" do
|
77
|
+
expect(cast.usec).to eq(123456)
|
78
|
+
expect(cast.strftime("%:z")).to eq("+08:00")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe ".serialize" do
|
84
|
+
subject(:serialize) { described_class.serialize(value) }
|
85
|
+
|
86
|
+
context "with nil value" do
|
87
|
+
let(:value) { nil }
|
88
|
+
|
89
|
+
it { is_expected.to be_nil }
|
90
|
+
end
|
91
|
+
|
92
|
+
context "with UTC Time" do
|
93
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+00:00") }
|
94
|
+
|
95
|
+
it "serializes with +00:00 offset" do
|
96
|
+
expect(serialize).to eq("2024-01-01T12:00:00+00:00")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with positive offset" do
|
101
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+08:00") }
|
102
|
+
|
103
|
+
it "retains positive offset in serialized form" do
|
104
|
+
expect(serialize).to eq("2024-01-01T12:00:00+08:00")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with negative offset" do
|
109
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "-05:00") }
|
110
|
+
|
111
|
+
it "retains negative offset in serialized form" do
|
112
|
+
expect(serialize).to eq("2024-01-01T12:00:00-05:00")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with fractional offset" do
|
117
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0, "+05:30") }
|
118
|
+
|
119
|
+
it "retains fractional offset in serialized form" do
|
120
|
+
expect(serialize).to eq("2024-01-01T12:00:00+05:30")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with fractional seconds" do
|
125
|
+
let(:value) { Time.new(2024, 1, 1, 12, 0, 0.5, "+08:00") }
|
126
|
+
|
127
|
+
xit "retains both fractional seconds and offset" do
|
128
|
+
expect(serialize).to eq("2024-01-01T12:00:00.500+08:00")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with microsecond precision" do
|
133
|
+
let(:value) { Time.at(Time.new(2024, 1, 1, 12).to_i, 123456, :usec) }
|
134
|
+
|
135
|
+
before { value.localtime("+08:00") }
|
136
|
+
|
137
|
+
xit "retains microsecond precision and offset" do
|
138
|
+
expect(serialize).to eq("2024-01-01T12:00:00.123456+08:00")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Lutaml::Model::Type::TimeWithoutDate do
|
4
|
+
describe ".cast" do
|
5
|
+
subject(:cast) { described_class.cast(value) }
|
6
|
+
|
7
|
+
context "with nil value" do
|
8
|
+
let(:value) { nil }
|
9
|
+
|
10
|
+
it { is_expected.to be_nil }
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with time string" do
|
14
|
+
let(:value) { "13:45:30" }
|
15
|
+
|
16
|
+
it "parses time correctly" do
|
17
|
+
expect(cast.hour).to eq(13)
|
18
|
+
expect(cast.min).to eq(45)
|
19
|
+
expect(cast.sec).to eq(30)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with time string with milliseconds" do
|
24
|
+
let(:value) { "13:45:30.500" }
|
25
|
+
|
26
|
+
it "parses time with milliseconds" do
|
27
|
+
expect(cast.hour).to eq(13)
|
28
|
+
expect(cast.min).to eq(45)
|
29
|
+
expect(cast.sec).to eq(30)
|
30
|
+
expect(cast.nsec).to eq(500_000_000)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with Time object" do
|
35
|
+
let(:value) { Time.new(2024, 1, 1, 13, 45, 30) }
|
36
|
+
|
37
|
+
it "extracts time components" do
|
38
|
+
expect(cast.hour).to eq(13)
|
39
|
+
expect(cast.min).to eq(45)
|
40
|
+
expect(cast.sec).to eq(30)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with DateTime object" do
|
45
|
+
let(:value) { DateTime.new(2024, 1, 1, 13, 45, 30) }
|
46
|
+
|
47
|
+
it "extracts time components" do
|
48
|
+
expect(cast.hour).to eq(13)
|
49
|
+
expect(cast.min).to eq(45)
|
50
|
+
expect(cast.sec).to eq(30)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with invalid time string" do
|
55
|
+
let(:value) { "not a time" }
|
56
|
+
|
57
|
+
it { is_expected.to be_nil }
|
58
|
+
end
|
59
|
+
|
60
|
+
context "with invalid hours" do
|
61
|
+
let(:value) { "24:00:00" }
|
62
|
+
|
63
|
+
xit { is_expected.to be_nil }
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with invalid minutes" do
|
67
|
+
let(:value) { "12:60:00" }
|
68
|
+
|
69
|
+
it { is_expected.to be_nil }
|
70
|
+
end
|
71
|
+
|
72
|
+
context "with invalid seconds" do
|
73
|
+
let(:value) { "12:00:61" }
|
74
|
+
|
75
|
+
it { is_expected.to be_nil }
|
76
|
+
end
|
77
|
+
|
78
|
+
context "with microsecond precision" do
|
79
|
+
let(:value) { "13:45:30.123456" }
|
80
|
+
|
81
|
+
it "retains microsecond precision" do
|
82
|
+
expect(cast.hour).to eq(13)
|
83
|
+
expect(cast.min).to eq(45)
|
84
|
+
expect(cast.sec).to eq(30)
|
85
|
+
expect(cast.usec).to eq(123456)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe ".serialize" do
|
91
|
+
subject(:serialize) { described_class.serialize(value) }
|
92
|
+
|
93
|
+
context "with nil value" do
|
94
|
+
let(:value) { nil }
|
95
|
+
|
96
|
+
it { is_expected.to be_nil }
|
97
|
+
end
|
98
|
+
|
99
|
+
context "with Time object" do
|
100
|
+
let(:value) { Time.new(2024, 1, 1, 13, 45, 30) }
|
101
|
+
|
102
|
+
it { is_expected.to eq("13:45:30") }
|
103
|
+
end
|
104
|
+
|
105
|
+
context "with single-digit values" do
|
106
|
+
let(:value) { Time.new(2024, 1, 1, 9, 5, 3) }
|
107
|
+
|
108
|
+
it "zero-pads values" do
|
109
|
+
expect(serialize).to eq("09:05:03")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with double-digit values" do
|
114
|
+
let(:value) { Time.new(2024, 1, 1, 13, 45, 30) }
|
115
|
+
|
116
|
+
it { is_expected.to eq("13:45:30") }
|
117
|
+
end
|
118
|
+
|
119
|
+
context "with zero values" do
|
120
|
+
let(:value) { Time.new(2024, 1, 1, 0, 0, 0) }
|
121
|
+
|
122
|
+
it { is_expected.to eq("00:00:00") }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|