resync 0.1.0

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.
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