resync 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +42 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/Gemfile +3 -0
- data/LICENSE.md +22 -0
- data/README.md +92 -0
- data/Rakefile +56 -0
- data/example.rb +100 -0
- data/lib/resync/capability_list.rb +85 -0
- data/lib/resync/change_dump.rb +15 -0
- data/lib/resync/change_dump_manifest.rb +15 -0
- data/lib/resync/change_list.rb +15 -0
- data/lib/resync/change_list_index.rb +26 -0
- data/lib/resync/link.rb +87 -0
- data/lib/resync/metadata.rb +112 -0
- data/lib/resync/resource.rb +72 -0
- data/lib/resync/resource_dump.rb +15 -0
- data/lib/resync/resource_dump_manifest.rb +15 -0
- data/lib/resync/resource_list.rb +15 -0
- data/lib/resync/resource_list_index.rb +15 -0
- data/lib/resync/shared/augmented.rb +76 -0
- data/lib/resync/shared/base_resource_list.rb +117 -0
- data/lib/resync/shared/descriptor.rb +135 -0
- data/lib/resync/shared/sitemap_index.rb +32 -0
- data/lib/resync/shared/sorted_resource_list.rb +60 -0
- data/lib/resync/source_description.rb +14 -0
- data/lib/resync/types/change.rb +14 -0
- data/lib/resync/types/change_frequency.rb +18 -0
- data/lib/resync/types.rb +6 -0
- data/lib/resync/version.rb +4 -0
- data/lib/resync/xml.rb +216 -0
- data/lib/resync/xml_parser.rb +65 -0
- data/lib/resync.rb +4 -0
- data/resync.gemspec +36 -0
- data/spec/acceptance/xml_parser_spec.rb +1049 -0
- data/spec/data/examples/README.md +1 -0
- data/spec/data/examples/example-1.xml +12 -0
- data/spec/data/examples/example-12.xml +25 -0
- data/spec/data/examples/example-13.xml +25 -0
- data/spec/data/examples/example-14.xml +23 -0
- data/spec/data/examples/example-15.xml +21 -0
- data/spec/data/examples/example-16.xml +24 -0
- data/spec/data/examples/example-17.xml +39 -0
- data/spec/data/examples/example-18.xml +25 -0
- data/spec/data/examples/example-19.xml +28 -0
- data/spec/data/examples/example-2.xml +18 -0
- data/spec/data/examples/example-20.xml +22 -0
- data/spec/data/examples/example-21.xml +31 -0
- data/spec/data/examples/example-22.xml +41 -0
- data/spec/data/examples/example-23.xml +41 -0
- data/spec/data/examples/example-24.xml +28 -0
- data/spec/data/examples/example-25.xml +21 -0
- data/spec/data/examples/example-26.xml +18 -0
- data/spec/data/examples/example-27.xml +36 -0
- data/spec/data/examples/example-28.xml +34 -0
- data/spec/data/examples/example-29.xml +27 -0
- data/spec/data/examples/example-3.xml +17 -0
- data/spec/data/examples/example-30.xml +18 -0
- data/spec/data/examples/example-31.xml +16 -0
- data/spec/data/examples/example-32.xml +22 -0
- data/spec/data/examples/example-33.xml +22 -0
- data/spec/data/examples/example-4.xml +10 -0
- data/spec/data/examples/example-5.xml +18 -0
- data/spec/data/examples/example-6.xml +21 -0
- data/spec/data/examples/example-7.xml +13 -0
- data/spec/data/examples/example-8.xml +12 -0
- data/spec/data/resourcesync.xsd +148 -0
- data/spec/data/siteindex.xsd +75 -0
- data/spec/data/sitemap.xsd +116 -0
- data/spec/rspec_custom_matchers.rb +89 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/todo.rb +11 -0
- data/spec/unit/resync/capability_list_spec.rb +138 -0
- data/spec/unit/resync/change_dump_manifest_spec.rb +75 -0
- data/spec/unit/resync/change_dump_spec.rb +61 -0
- data/spec/unit/resync/change_list_index_spec.rb +49 -0
- data/spec/unit/resync/change_list_spec.rb +75 -0
- data/spec/unit/resync/link_spec.rb +93 -0
- data/spec/unit/resync/metadata_spec.rb +169 -0
- data/spec/unit/resync/resource_dump_manifest_spec.rb +59 -0
- data/spec/unit/resync/resource_dump_spec.rb +62 -0
- data/spec/unit/resync/resource_list_index_spec.rb +53 -0
- data/spec/unit/resync/resource_list_spec.rb +60 -0
- data/spec/unit/resync/resource_spec.rb +176 -0
- data/spec/unit/resync/shared/augmented_examples.rb +58 -0
- data/spec/unit/resync/shared/base_resource_list_examples.rb +103 -0
- data/spec/unit/resync/shared/descriptor_examples.rb +122 -0
- data/spec/unit/resync/shared/descriptor_spec.rb +33 -0
- data/spec/unit/resync/shared/sorted_list_examples.rb +134 -0
- data/spec/unit/resync/shared/uri_field_examples.rb +36 -0
- data/spec/unit/resync/source_description_spec.rb +55 -0
- data/spec/unit/resync/xml/timenode_spec.rb +48 -0
- data/spec/unit/resync/xml/xml_spec.rb +40 -0
- data/spec/unit/resync/xml_parser_spec.rb +82 -0
- metadata +340 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
module Resync
|
2
|
+
RSpec.shared_examples Descriptor do
|
3
|
+
|
4
|
+
# TODO: Find a better way to express this
|
5
|
+
def new_instance(**args)
|
6
|
+
required_args = (defined? required_arguments) ? required_arguments : {}
|
7
|
+
args = required_args.merge(args)
|
8
|
+
described_class.new(**args)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'modified_time' do
|
12
|
+
it 'accepts a modified timestamp' do
|
13
|
+
modified_time = Time.utc(1997, 7, 16, 19, 20, 30.45)
|
14
|
+
descriptor = new_instance(modified_time: modified_time)
|
15
|
+
expect(descriptor.modified_time).to be_time(modified_time)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'defaults to nil if no modified timestamp is specified' do
|
19
|
+
descriptor = new_instance
|
20
|
+
expect(descriptor.modified_time).to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'fails if the modified timestamp is not a time' do
|
24
|
+
expect { new_instance(modified_time: '12:45 pm') }.to raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'length' do
|
29
|
+
it 'accepts a length' do
|
30
|
+
length = 12_345
|
31
|
+
descriptor = new_instance(length: length)
|
32
|
+
expect(descriptor.length).to eq(length)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'defaults to nil if no length is specified' do
|
36
|
+
descriptor = new_instance
|
37
|
+
expect(descriptor.length).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'fails if length is not a non-negative integer' do
|
41
|
+
expect { new_instance(length: -12_345) }.to raise_error(ArgumentError)
|
42
|
+
expect { new_instance(length: 123.45) }.to raise_error(ArgumentError)
|
43
|
+
expect { new_instance(length: 'I am not a number') }.to raise_error(ArgumentError)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'mime_type' do
|
48
|
+
it 'accepts a standard MIME type' do
|
49
|
+
mt = MIME::Types['text/plain'].first
|
50
|
+
descriptor = new_instance(mime_type: mt)
|
51
|
+
expect(descriptor.mime_type).to eq(mt)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'accepts a non-standard MIME type' do
|
55
|
+
mt = MIME::Type.new('elvis/presley')
|
56
|
+
descriptor = new_instance(mime_type: mt)
|
57
|
+
expect(descriptor.mime_type).to eq(mt)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'accepts a MIME type as a string' do
|
61
|
+
mt_string = 'elvis/presley'
|
62
|
+
descriptor = new_instance(mime_type: mt_string)
|
63
|
+
expect(descriptor.mime_type).to eq(MIME::Type.new(mt_string))
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'defaults to nil if no MIME type is specified' do
|
67
|
+
descriptor = new_instance
|
68
|
+
expect(descriptor.length).to be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'fails if mime_type isn\'t a MIME type' do
|
72
|
+
mt_string = 'I am not a mime type'
|
73
|
+
expect { new_instance(mime_type: mt_string) }.to raise_error(MIME::Type::InvalidContentType)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'encoding' do
|
78
|
+
it 'accepts an encoding' do
|
79
|
+
encoding = 'utf-8'
|
80
|
+
descriptor = new_instance(encoding: encoding)
|
81
|
+
expect(descriptor.encoding).to eq(encoding)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'defaults to nil if no encoding specified' do
|
85
|
+
descriptor = new_instance
|
86
|
+
expect(descriptor.encoding).to be_nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'hash' do
|
91
|
+
it 'accepts a hash of hashes' do
|
92
|
+
hashes = {
|
93
|
+
'md5' => '1e0d5cb8ef6ba40c99b14c0237be735e',
|
94
|
+
'sha-256' => '854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784'
|
95
|
+
}
|
96
|
+
descriptor = new_instance(hashes: hashes)
|
97
|
+
expect(descriptor.hashes).to eq(hashes)
|
98
|
+
expect(descriptor.hash('md5')). to eq('1e0d5cb8ef6ba40c99b14c0237be735e')
|
99
|
+
expect(descriptor.hash('sha-256')). to eq('854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784')
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'defaults to an empty hash if no hash specified' do
|
103
|
+
descriptor = new_instance
|
104
|
+
expect(descriptor.hashes).to eq({})
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'path' do
|
109
|
+
it 'accepts a path' do
|
110
|
+
path = '/resources/res2'
|
111
|
+
descriptor = new_instance(path: path)
|
112
|
+
expect(descriptor.path).to eq(path)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'defaults to nil if no path specified' do
|
116
|
+
descriptor = new_instance
|
117
|
+
expect(descriptor.path).to be_nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
describe Descriptor do
|
5
|
+
describe 'hash_of_hashcodes' do
|
6
|
+
it 'extracts a hash' do
|
7
|
+
hash_str = 'md5:1e0d5cb8ef6ba40c99b14c0237be735e'
|
8
|
+
hashes = Descriptor.hash_of_hashcodes(hash_str)
|
9
|
+
expect(hashes['md5']).to eq('1e0d5cb8ef6ba40c99b14c0237be735e')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'extracts multiple hashes separated by a space' do
|
13
|
+
hash_str = 'md5:1e0d5cb8ef6ba40c99b14c0237be735e sha-256:854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784'
|
14
|
+
hashes = Descriptor.hash_of_hashcodes(hash_str)
|
15
|
+
expect(hashes['md5']).to eq('1e0d5cb8ef6ba40c99b14c0237be735e')
|
16
|
+
expect(hashes['sha-256']).to eq('854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'extracts multiple hashes separated by arbitrary whitespace' do
|
20
|
+
hash_str = 'md5:1e0d5cb8ef6ba40c99b14c0237be735e
|
21
|
+
sha-256:854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784'
|
22
|
+
hashes = Descriptor.hash_of_hashcodes(hash_str)
|
23
|
+
expect(hashes['md5']).to eq('1e0d5cb8ef6ba40c99b14c0237be735e')
|
24
|
+
expect(hashes['sha-256']).to eq('854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'leaves a hash of hashes alone' do
|
28
|
+
hashes = { 'md5' => '1e0d5cb8ef6ba40c99b14c0237be735e', 'sha-256' => '854f61290e2e197a11bc91063afce22e43f8ccc655237050ace766adc68dc784' }
|
29
|
+
expect(Descriptor.hash_of_hashcodes(hashes)).to eq(hashes)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require_relative 'base_resource_list_examples'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
|
5
|
+
RSpec.shared_examples SortedResourceList do
|
6
|
+
|
7
|
+
# ------------------------------------------------------
|
8
|
+
# Superclass conformance
|
9
|
+
|
10
|
+
it_behaves_like BaseResourceList
|
11
|
+
|
12
|
+
def new_instance(**args)
|
13
|
+
required_args = (defined? required_arguments) ? required_arguments : {}
|
14
|
+
args = required_args.merge(args)
|
15
|
+
described_class.new(**args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# ------------------------------------------------------
|
19
|
+
# Tests
|
20
|
+
|
21
|
+
describe '#new' do
|
22
|
+
describe 'resources' do
|
23
|
+
it 'sorts resources by modified_time' do
|
24
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
25
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
26
|
+
list = new_instance(resources: [resource1, resource0])
|
27
|
+
expect(list.resources).to eq([resource0, resource1])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sorts resources with modified_time before resources without' do
|
31
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
32
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
33
|
+
resource2 = Resource.new(uri: 'http://example.com')
|
34
|
+
list = new_instance(resources: [resource1, resource2, resource0])
|
35
|
+
expect(list.resources).to eq([resource0, resource1, resource2])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#resources_by_uri' do
|
41
|
+
it 'groups resources by uri' do
|
42
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
43
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
44
|
+
resource2 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1994, 7, 16, 19, 20, 30.45))
|
45
|
+
resource3 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1995, 7, 16, 19, 20, 30.45))
|
46
|
+
list = new_instance(resources: [resource0, resource1, resource2, resource3])
|
47
|
+
|
48
|
+
by_uri = list.resources_by_uri
|
49
|
+
expect(by_uri.size).to eq(2)
|
50
|
+
|
51
|
+
uri0 = by_uri.keys[0]
|
52
|
+
expect(uri0).to eq(URI('http://example.com'))
|
53
|
+
rs0 = by_uri[uri0]
|
54
|
+
expect(rs0).to eq([resource2, resource3])
|
55
|
+
|
56
|
+
uri1 = by_uri.keys[1]
|
57
|
+
expect(uri1).to eq(URI('http://example.org'))
|
58
|
+
rs1 = by_uri[uri1]
|
59
|
+
expect(rs1).to eq([resource0, resource1])
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'updates when the list is updated' do
|
63
|
+
list = new_instance
|
64
|
+
expect(list.resources_by_uri).to eq({})
|
65
|
+
|
66
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
67
|
+
list.resources = [resource0]
|
68
|
+
|
69
|
+
by_uri = list.resources_by_uri
|
70
|
+
expect(by_uri.size).to eq(1)
|
71
|
+
rs0 = by_uri[URI('http://example.org')]
|
72
|
+
expect(rs0).to eq([resource0])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#latest_for' do
|
77
|
+
it 'retrieves the last-modified-instance for a specified URI' do
|
78
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
79
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
80
|
+
resource2 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1994, 7, 16, 19, 20, 30.45))
|
81
|
+
resource3 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1995, 7, 16, 19, 20, 30.45))
|
82
|
+
resource4 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(2003, 7, 16, 19, 20, 30.45))
|
83
|
+
list = new_instance(resources: [resource0, resource1, resource2, resource3, resource4])
|
84
|
+
|
85
|
+
expect(list.latest_for(uri: URI('http://example.org'))).to eq(resource1)
|
86
|
+
expect(list.latest_for(uri: 'http://example.com')).to eq(resource4)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'works when loading from XML' do
|
90
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30))
|
91
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30))
|
92
|
+
resource2 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1994, 7, 16, 19, 20, 30))
|
93
|
+
resource3 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1995, 7, 16, 19, 20, 30))
|
94
|
+
resource4 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(2003, 7, 16, 19, 20, 30))
|
95
|
+
list = new_instance(resources: [resource0, resource1, resource2, resource3, resource4])
|
96
|
+
xml = list.save_to_xml
|
97
|
+
list = described_class.load_from_xml(xml)
|
98
|
+
|
99
|
+
latest_org = list.latest_for(uri: URI('http://example.org'))
|
100
|
+
expect(latest_org.uri).to eq(resource1.uri)
|
101
|
+
expect(latest_org.modified_time).to be_time(resource1.modified_time)
|
102
|
+
latest_com = list.latest_for(uri: 'http://example.com')
|
103
|
+
expect(latest_com.uri).to eq(resource4.uri)
|
104
|
+
expect(latest_com.modified_time).to be_time(resource4.modified_time)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#all_uris' do
|
109
|
+
it 'retrieves the set of all URIs' do
|
110
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
111
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
112
|
+
resource2 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1994, 7, 16, 19, 20, 30.45))
|
113
|
+
resource3 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1995, 7, 16, 19, 20, 30.45))
|
114
|
+
resource4 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(2003, 7, 16, 19, 20, 30.45))
|
115
|
+
list = new_instance(resources: [resource0, resource1, resource2, resource3, resource4])
|
116
|
+
expect(list.all_uris).to eq([URI('http://example.org'), URI('http://example.com')])
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'works when loading from XML' do
|
120
|
+
resource0 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1997, 7, 16, 19, 20, 30.45))
|
121
|
+
resource1 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1998, 7, 16, 19, 20, 30.45))
|
122
|
+
resource2 = Resource.new(uri: 'http://example.org', modified_time: Time.utc(1994, 7, 16, 19, 20, 30.45))
|
123
|
+
resource3 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(1995, 7, 16, 19, 20, 30.45))
|
124
|
+
resource4 = Resource.new(uri: 'http://example.com', modified_time: Time.utc(2003, 7, 16, 19, 20, 30.45))
|
125
|
+
list = new_instance(resources: [resource0, resource1, resource2, resource3, resource4])
|
126
|
+
xml = list.save_to_xml
|
127
|
+
list = described_class.load_from_xml(xml)
|
128
|
+
|
129
|
+
expect(list.all_uris).to eq([URI('http://example.org'), URI('http://example.com')])
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
RSpec.shared_examples 'a URI field' do
|
5
|
+
|
6
|
+
# TODO: Find a better way to express this
|
7
|
+
def new_instance(**args)
|
8
|
+
required_args = (defined? required_arguments) ? required_arguments : {}
|
9
|
+
required_args.delete(:uri)
|
10
|
+
args = required_args.merge(args)
|
11
|
+
described_class.new(**args)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'accepts a URI' do
|
15
|
+
uri = URI('http://example.org/')
|
16
|
+
instance = new_instance(uri: uri)
|
17
|
+
expect(instance.uri).to eq(uri)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'accepts a string URI' do
|
21
|
+
uri = 'http://example.org/'
|
22
|
+
instance = new_instance(uri: uri)
|
23
|
+
expect(instance.uri).to eq(URI(uri))
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'rejects an invalid URI' do
|
27
|
+
invalid_url = 'I am not a valid URI'
|
28
|
+
expect { new_instance(uri: invalid_url) }.to raise_error(URI::InvalidURIError)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'requires a URI' do
|
32
|
+
expect { new_instance }.to raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared/base_resource_list_examples'
|
3
|
+
require_relative 'shared/augmented_examples'
|
4
|
+
|
5
|
+
module Resync
|
6
|
+
describe SourceDescription do
|
7
|
+
it_behaves_like BaseResourceList
|
8
|
+
|
9
|
+
describe 'links' do
|
10
|
+
it_behaves_like Augmented
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'XML conversion' do
|
14
|
+
describe '#from_xml' do
|
15
|
+
it 'parses an XML string' do
|
16
|
+
data = File.read('spec/data/examples/example-12.xml')
|
17
|
+
list = SourceDescription.load_from_xml(XML.element(data))
|
18
|
+
|
19
|
+
links = list.links
|
20
|
+
expect(links.size).to eq(1)
|
21
|
+
link = links[0]
|
22
|
+
expect(link.rel).to eq('describedby')
|
23
|
+
expect(link.uri).to eq(URI('http://example.com/info_about_source.xml'))
|
24
|
+
|
25
|
+
md = list.metadata
|
26
|
+
expect(md.capability).to eq('description')
|
27
|
+
|
28
|
+
urls = list.resources
|
29
|
+
expect(urls.size).to eq(3)
|
30
|
+
|
31
|
+
(0..2).each do |i|
|
32
|
+
url = urls[i]
|
33
|
+
expect(url.uri).to eq(URI("http://example.com/capabilitylist#{i + 1}.xml"))
|
34
|
+
md = url.metadata
|
35
|
+
expect(md.capability).to eq('capabilitylist')
|
36
|
+
links = url.links
|
37
|
+
expect(links.size).to eq(1)
|
38
|
+
link = links[0]
|
39
|
+
expect(link.rel).to eq('describedby')
|
40
|
+
expect(link.uri).to eq(URI("http://example.com/info_about_set#{i + 1}_of_resources.xml"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#save_to_xml' do
|
46
|
+
it 'can round-trip to XML' do
|
47
|
+
data = File.read('spec/data/examples/example-12.xml')
|
48
|
+
list = SourceDescription.load_from_xml(XML.element(data))
|
49
|
+
xml = list.save_to_xml
|
50
|
+
expect(xml).to be_xml(data)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
module XML
|
5
|
+
|
6
|
+
class Elem
|
7
|
+
include ::XML::Mapping
|
8
|
+
time_node :time, '@time', default_value: nil
|
9
|
+
|
10
|
+
def self.from_str(time_str)
|
11
|
+
xml_string = "<elem time='#{time_str}'/>"
|
12
|
+
doc = REXML::Document.new(xml_string)
|
13
|
+
load_from_xml(doc.root)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe TimeNode do
|
18
|
+
|
19
|
+
def time(str)
|
20
|
+
Elem.from_str(str).time
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'parses a date with hours, minutes, and seconds' do
|
24
|
+
actual = time('1997-07-16T19:20:30')
|
25
|
+
expected = Time.new(1997, 7, 16, 19, 20, 30)
|
26
|
+
expect(actual).to be_time(expected)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'parses a date with hours, minutes, seconds, and fractional seconds' do
|
30
|
+
actual = time('1997-07-16T19:20:30.45')
|
31
|
+
expected = Time.new(1997, 7, 16, 19, 20, 30.45)
|
32
|
+
expect(actual).to be_time(expected)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'parses a UTC "zulu" time (time zone designator "Z")' do
|
36
|
+
actual = time('1997-07-16T19:20:30.45Z')
|
37
|
+
expected = Time.new(1997, 7, 16, 19, 20, 30.45, '+00:00')
|
38
|
+
expect(actual).to be_time(expected)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'parses a time with a numeric timezone offset' do
|
42
|
+
actual = time('1997-07-16T19:20:30.45+01:30')
|
43
|
+
expected = Time.new(1997, 7, 16, 19, 20, 30.45, '+01:30')
|
44
|
+
expect(actual).to be_time(expected)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
describe XML do
|
5
|
+
describe '#element' do
|
6
|
+
it 'returns an element unchanged' do
|
7
|
+
elem = REXML::Element.new('foo')
|
8
|
+
expect(XML.element(elem)).to be(elem)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'returns the root element of a string document' do
|
12
|
+
xml_str = '<?xml version="1.0"?><foo><bar/><baz/></foo>'
|
13
|
+
elem = XML.element(xml_str)
|
14
|
+
expect(elem).to be_a(REXML::Element)
|
15
|
+
expect(elem).to be_xml('<foo><bar/><baz/></foo>')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns the root element of a REXML::Document' do
|
19
|
+
xml_str = '<?xml version="1.0"?><foo><bar/><baz/></foo>'
|
20
|
+
doc = REXML::Document.new(xml_str)
|
21
|
+
elem = XML.element(doc)
|
22
|
+
expect(elem).to be_a(REXML::Element)
|
23
|
+
expect(elem).to be_xml(doc.root)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'parses an XML fragment as an element' do
|
27
|
+
xml_str = '<foo><bar/><baz/></foo>'
|
28
|
+
elem = XML.element(xml_str)
|
29
|
+
expect(elem).to be_a(REXML::Element)
|
30
|
+
expect(elem).to be_xml(xml_str)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'fails when it gets something other than XML' do
|
34
|
+
data = 12_345
|
35
|
+
expect { XML.element(data) }.to raise_exception
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Resync
|
4
|
+
describe XMLParser do
|
5
|
+
|
6
|
+
describe '#parse' do
|
7
|
+
it 'parses an XML document' do
|
8
|
+
data = File.read('spec/data/examples/example-1.xml')
|
9
|
+
doc = REXML::Document.new(data)
|
10
|
+
urlset = XMLParser.parse(doc)
|
11
|
+
expect(urlset).to be_a(ResourceList)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'parses a urlset' do
|
15
|
+
data = File.read('spec/data/examples/example-1.xml')
|
16
|
+
root = REXML::Document.new(data).root
|
17
|
+
urlset = XMLParser.parse(root)
|
18
|
+
expect(urlset).to be_a(ResourceList)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'parses a sitemapindex' do
|
22
|
+
data = File.read('spec/data/examples/example-8.xml')
|
23
|
+
root = REXML::Document.new(data).root
|
24
|
+
sitemapindex = XMLParser.parse(root)
|
25
|
+
expect(sitemapindex).to be_a(ResourceListIndex)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'parses a String' do
|
29
|
+
data = File.read('spec/data/examples/example-1.xml')
|
30
|
+
urlset = XMLParser.parse(data)
|
31
|
+
expect(urlset).to be_a(ResourceList)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'parses an XML fragment' do
|
35
|
+
data = '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:rs="http://www.openarchives.org/rs/terms/">
|
36
|
+
<rs:md capability="resourcelist" at="2013-01-03T09:00:00Z"/>
|
37
|
+
<url><loc>http://example.com/res1</loc></url>
|
38
|
+
</urlset>'
|
39
|
+
urlset = XMLParser.parse(data)
|
40
|
+
expect(urlset).to be_a(ResourceList)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'fails if the root element has no metadata' do
|
44
|
+
data = '<?xml version="1.0" encoding="UTF-8"?>
|
45
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:rs="http://www.openarchives.org/rs/terms/">
|
46
|
+
<url><loc>http://example.com/res1</loc></url>
|
47
|
+
</urlset>'
|
48
|
+
expect { XMLParser.parse(data) }.to raise_error(ArgumentError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'fails if the root element\'s metadata has no capability attribute' do
|
52
|
+
data = '<?xml version="1.0" encoding="UTF-8"?>
|
53
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:rs="http://www.openarchives.org/rs/terms/">
|
54
|
+
<rs:md at="2013-01-03T09:00:00Z"/>
|
55
|
+
<url><loc>http://example.com/res1</loc></url>
|
56
|
+
</urlset>'
|
57
|
+
expect { XMLParser.parse(data) }.to raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'fails if the root element\'s metadata has an unknown capability attribute' do
|
61
|
+
data = '<?xml version="1.0" encoding="UTF-8"?>
|
62
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:rs="http://www.openarchives.org/rs/terms/">
|
63
|
+
<rs:md capability="NOT A CAPABILITY" at="2013-01-03T09:00:00Z"/>
|
64
|
+
<url><loc>http://example.com/res1</loc></url>
|
65
|
+
</urlset>'
|
66
|
+
expect { XMLParser.parse(data) }.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'fails when it gets something other than a <urlset/> or <sitemapindex/>' do
|
70
|
+
data = '<loc>http://example.com/resourcelist-part1.xml</loc>'
|
71
|
+
expect { XMLParser.parse(data) }.to raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'fails when it gets something other than XML' do
|
75
|
+
data = 12_345
|
76
|
+
expect { XMLParser.parse(data) }.to raise_error(ArgumentError)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|