resync 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rubocop.yml +23 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +2 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.md +22 -0
  8. data/README.md +92 -0
  9. data/Rakefile +56 -0
  10. data/example.rb +100 -0
  11. data/lib/resync/capability_list.rb +85 -0
  12. data/lib/resync/change_dump.rb +15 -0
  13. data/lib/resync/change_dump_manifest.rb +15 -0
  14. data/lib/resync/change_list.rb +15 -0
  15. data/lib/resync/change_list_index.rb +26 -0
  16. data/lib/resync/link.rb +87 -0
  17. data/lib/resync/metadata.rb +112 -0
  18. data/lib/resync/resource.rb +72 -0
  19. data/lib/resync/resource_dump.rb +15 -0
  20. data/lib/resync/resource_dump_manifest.rb +15 -0
  21. data/lib/resync/resource_list.rb +15 -0
  22. data/lib/resync/resource_list_index.rb +15 -0
  23. data/lib/resync/shared/augmented.rb +76 -0
  24. data/lib/resync/shared/base_resource_list.rb +117 -0
  25. data/lib/resync/shared/descriptor.rb +135 -0
  26. data/lib/resync/shared/sitemap_index.rb +32 -0
  27. data/lib/resync/shared/sorted_resource_list.rb +60 -0
  28. data/lib/resync/source_description.rb +14 -0
  29. data/lib/resync/types/change.rb +14 -0
  30. data/lib/resync/types/change_frequency.rb +18 -0
  31. data/lib/resync/types.rb +6 -0
  32. data/lib/resync/version.rb +4 -0
  33. data/lib/resync/xml.rb +216 -0
  34. data/lib/resync/xml_parser.rb +65 -0
  35. data/lib/resync.rb +4 -0
  36. data/resync.gemspec +36 -0
  37. data/spec/acceptance/xml_parser_spec.rb +1049 -0
  38. data/spec/data/examples/README.md +1 -0
  39. data/spec/data/examples/example-1.xml +12 -0
  40. data/spec/data/examples/example-12.xml +25 -0
  41. data/spec/data/examples/example-13.xml +25 -0
  42. data/spec/data/examples/example-14.xml +23 -0
  43. data/spec/data/examples/example-15.xml +21 -0
  44. data/spec/data/examples/example-16.xml +24 -0
  45. data/spec/data/examples/example-17.xml +39 -0
  46. data/spec/data/examples/example-18.xml +25 -0
  47. data/spec/data/examples/example-19.xml +28 -0
  48. data/spec/data/examples/example-2.xml +18 -0
  49. data/spec/data/examples/example-20.xml +22 -0
  50. data/spec/data/examples/example-21.xml +31 -0
  51. data/spec/data/examples/example-22.xml +41 -0
  52. data/spec/data/examples/example-23.xml +41 -0
  53. data/spec/data/examples/example-24.xml +28 -0
  54. data/spec/data/examples/example-25.xml +21 -0
  55. data/spec/data/examples/example-26.xml +18 -0
  56. data/spec/data/examples/example-27.xml +36 -0
  57. data/spec/data/examples/example-28.xml +34 -0
  58. data/spec/data/examples/example-29.xml +27 -0
  59. data/spec/data/examples/example-3.xml +17 -0
  60. data/spec/data/examples/example-30.xml +18 -0
  61. data/spec/data/examples/example-31.xml +16 -0
  62. data/spec/data/examples/example-32.xml +22 -0
  63. data/spec/data/examples/example-33.xml +22 -0
  64. data/spec/data/examples/example-4.xml +10 -0
  65. data/spec/data/examples/example-5.xml +18 -0
  66. data/spec/data/examples/example-6.xml +21 -0
  67. data/spec/data/examples/example-7.xml +13 -0
  68. data/spec/data/examples/example-8.xml +12 -0
  69. data/spec/data/resourcesync.xsd +148 -0
  70. data/spec/data/siteindex.xsd +75 -0
  71. data/spec/data/sitemap.xsd +116 -0
  72. data/spec/rspec_custom_matchers.rb +89 -0
  73. data/spec/spec_helper.rb +31 -0
  74. data/spec/todo.rb +11 -0
  75. data/spec/unit/resync/capability_list_spec.rb +138 -0
  76. data/spec/unit/resync/change_dump_manifest_spec.rb +75 -0
  77. data/spec/unit/resync/change_dump_spec.rb +61 -0
  78. data/spec/unit/resync/change_list_index_spec.rb +49 -0
  79. data/spec/unit/resync/change_list_spec.rb +75 -0
  80. data/spec/unit/resync/link_spec.rb +93 -0
  81. data/spec/unit/resync/metadata_spec.rb +169 -0
  82. data/spec/unit/resync/resource_dump_manifest_spec.rb +59 -0
  83. data/spec/unit/resync/resource_dump_spec.rb +62 -0
  84. data/spec/unit/resync/resource_list_index_spec.rb +53 -0
  85. data/spec/unit/resync/resource_list_spec.rb +60 -0
  86. data/spec/unit/resync/resource_spec.rb +176 -0
  87. data/spec/unit/resync/shared/augmented_examples.rb +58 -0
  88. data/spec/unit/resync/shared/base_resource_list_examples.rb +103 -0
  89. data/spec/unit/resync/shared/descriptor_examples.rb +122 -0
  90. data/spec/unit/resync/shared/descriptor_spec.rb +33 -0
  91. data/spec/unit/resync/shared/sorted_list_examples.rb +134 -0
  92. data/spec/unit/resync/shared/uri_field_examples.rb +36 -0
  93. data/spec/unit/resync/source_description_spec.rb +55 -0
  94. data/spec/unit/resync/xml/timenode_spec.rb +48 -0
  95. data/spec/unit/resync/xml/xml_spec.rb +40 -0
  96. data/spec/unit/resync/xml_parser_spec.rb +82 -0
  97. 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