berkeley_library-tind 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.idea/inspectionProfiles/Project_Default.xml +20 -0
  5. data/.idea/misc.xml +4 -0
  6. data/.idea/modules.xml +8 -0
  7. data/.idea/tind.iml +138 -0
  8. data/.idea/vcs.xml +6 -0
  9. data/.rubocop.yml +334 -0
  10. data/.ruby-version +1 -0
  11. data/.simplecov +8 -0
  12. data/.yardopts +1 -0
  13. data/CHANGES.md +58 -0
  14. data/Dockerfile +57 -0
  15. data/Gemfile +3 -0
  16. data/Jenkinsfile +18 -0
  17. data/LICENSE.md +21 -0
  18. data/README.md +73 -0
  19. data/Rakefile +20 -0
  20. data/berkeley_library-tind.gemspec +50 -0
  21. data/bin/tind-export +14 -0
  22. data/docker-compose.yml +15 -0
  23. data/lib/berkeley_library/tind.rb +3 -0
  24. data/lib/berkeley_library/tind/api.rb +1 -0
  25. data/lib/berkeley_library/tind/api/api.rb +132 -0
  26. data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
  27. data/lib/berkeley_library/tind/api/collection.rb +82 -0
  28. data/lib/berkeley_library/tind/api/date_range.rb +67 -0
  29. data/lib/berkeley_library/tind/api/format.rb +32 -0
  30. data/lib/berkeley_library/tind/api/search.rb +100 -0
  31. data/lib/berkeley_library/tind/config.rb +103 -0
  32. data/lib/berkeley_library/tind/export.rb +1 -0
  33. data/lib/berkeley_library/tind/export/column.rb +54 -0
  34. data/lib/berkeley_library/tind/export/column_group.rb +144 -0
  35. data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
  36. data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
  37. data/lib/berkeley_library/tind/export/config.rb +154 -0
  38. data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
  39. data/lib/berkeley_library/tind/export/export.rb +47 -0
  40. data/lib/berkeley_library/tind/export/export_command.rb +168 -0
  41. data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
  42. data/lib/berkeley_library/tind/export/export_format.rb +67 -0
  43. data/lib/berkeley_library/tind/export/exporter.rb +105 -0
  44. data/lib/berkeley_library/tind/export/filter.rb +52 -0
  45. data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
  46. data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
  47. data/lib/berkeley_library/tind/export/row.rb +24 -0
  48. data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
  49. data/lib/berkeley_library/tind/export/table.rb +175 -0
  50. data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
  51. data/lib/berkeley_library/tind/marc.rb +1 -0
  52. data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
  53. data/lib/berkeley_library/tind/module_info.rb +14 -0
  54. data/lib/berkeley_library/util/arrays.rb +178 -0
  55. data/lib/berkeley_library/util/logging.rb +1 -0
  56. data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
  57. data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
  58. data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
  59. data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
  60. data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
  61. data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
  62. data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
  63. data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
  64. data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
  65. data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
  66. data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
  67. data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
  68. data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
  69. data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
  70. data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
  71. data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
  72. data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
  73. data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
  74. data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
  75. data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
  76. data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
  77. data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
  78. data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
  79. data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
  80. data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
  81. data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
  82. data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
  83. data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
  84. data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
  85. data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
  86. data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
  87. data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
  88. data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
  89. data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
  90. data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
  91. data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
  92. data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
  93. data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
  94. data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
  95. data/lib/berkeley_library/util/paths.rb +111 -0
  96. data/lib/berkeley_library/util/stringios.rb +30 -0
  97. data/lib/berkeley_library/util/strings.rb +42 -0
  98. data/lib/berkeley_library/util/sys_exits.rb +15 -0
  99. data/lib/berkeley_library/util/times.rb +22 -0
  100. data/lib/berkeley_library/util/uris.rb +44 -0
  101. data/lib/berkeley_library/util/uris/appender.rb +162 -0
  102. data/lib/berkeley_library/util/uris/requester.rb +62 -0
  103. data/lib/berkeley_library/util/uris/validator.rb +32 -0
  104. data/rakelib/bundle.rake +8 -0
  105. data/rakelib/coverage.rake +11 -0
  106. data/rakelib/gem.rake +54 -0
  107. data/rakelib/rubocop.rake +18 -0
  108. data/rakelib/spec.rake +2 -0
  109. data/spec/.rubocop.yml +40 -0
  110. data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
  111. data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
  112. data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
  113. data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
  114. data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
  115. data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
  116. data/spec/berkeley_library/tind/config_spec.rb +86 -0
  117. data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
  118. data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
  119. data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
  120. data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
  121. data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
  122. data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
  123. data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
  124. data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
  125. data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
  126. data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
  127. data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
  128. data/spec/berkeley_library/util/arrays_spec.rb +340 -0
  129. data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
  130. data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
  131. data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
  132. data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
  133. data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
  134. data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
  135. data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
  136. data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
  137. data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
  138. data/spec/berkeley_library/util/paths_spec.rb +90 -0
  139. data/spec/berkeley_library/util/stringios_spec.rb +34 -0
  140. data/spec/berkeley_library/util/strings_spec.rb +27 -0
  141. data/spec/berkeley_library/util/times_spec.rb +39 -0
  142. data/spec/berkeley_library/util/uris_spec.rb +118 -0
  143. data/spec/data/collection-names.txt +438 -0
  144. data/spec/data/collections.json +4827 -0
  145. data/spec/data/disjoint-records.xml +187 -0
  146. data/spec/data/record-184453.xml +58 -0
  147. data/spec/data/record-184458.xml +63 -0
  148. data/spec/data/record-187888.xml +78 -0
  149. data/spec/data/records-api-search-cjk-p1.xml +6381 -0
  150. data/spec/data/records-api-search-cjk-p2.xml +5 -0
  151. data/spec/data/records-api-search-p1.xml +4506 -0
  152. data/spec/data/records-api-search-p2.xml +4509 -0
  153. data/spec/data/records-api-search-p3.xml +4506 -0
  154. data/spec/data/records-api-search-p4.xml +4509 -0
  155. data/spec/data/records-api-search-p5.xml +4506 -0
  156. data/spec/data/records-api-search-p6.xml +2436 -0
  157. data/spec/data/records-api-search-p7.xml +5 -0
  158. data/spec/data/records-api-search.xml +234 -0
  159. data/spec/data/records-manual-search.xml +547 -0
  160. data/spec/spec_helper.rb +30 -0
  161. data/test/profile/table_from_records_profile.rb +46 -0
  162. metadata +585 -0
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module API
6
+ describe DateRange do
7
+
8
+ let(:t1) { Time.new(2021, 2, 15, 16, 23, 14, '-08:00') }
9
+ let(:t2) { Time.new(2021, 2, 16, 19, 23, 14, '-07:00') }
10
+
11
+ it 'is always in UTC' do
12
+ expect(t1.utc_offset).to eq(-28_800) # just to be sure
13
+ expect(t2.utc_offset).to eq(-25_200) # just to be sure
14
+
15
+ date_range = DateRange.new(from_time: t1, until_time: t2)
16
+ expect(date_range.from_time).to eq(t1.getutc)
17
+ expect(date_range.until_time).to eq(t2.getutc)
18
+
19
+ expect(t1.utc_offset).to eq(-28_800), 'DateRange initializer should not modify input from_time'
20
+ expect(t2.utc_offset).to eq(-25_200), 'DateRange initializer should not modify input until_time'
21
+ end
22
+
23
+ it 'rejects invalid ranges' do
24
+ expect { DateRange.new(from_time: t2, until_time: t1) }.to raise_error(ArgumentError)
25
+ end
26
+
27
+ describe :mtime do
28
+ it 'defaults to false' do
29
+ date_range = DateRange.new(from_time: t1, until_time: t2)
30
+ expect(date_range.mtime?).to eq(false)
31
+
32
+ params = date_range.to_params
33
+ expect(params.key?(:dt)).to eq(false)
34
+ end
35
+
36
+ it 'can be set to true' do
37
+ date_range = DateRange.new(from_time: t1, until_time: t2, mtime: true)
38
+ expect(date_range.mtime?).to eq(true)
39
+
40
+ params = date_range.to_params
41
+ expect(params[:dt]).to eq('m')
42
+ end
43
+ end
44
+
45
+ describe :to_params do
46
+ let(:tz_utc) { TZInfo::Timezone.get('UTC') }
47
+ let(:tz_local) { TZInfo::Timezone.get('Antarctica/South_Pole') }
48
+
49
+ before(:each) do
50
+ @tz_orig = BerkeleyLibrary::TIND::Config.instance_variable_get(:@timezone)
51
+ end
52
+
53
+ after(:each) do
54
+ BerkeleyLibrary::TIND::Config.instance_variable_set(:@timezone, @tz_orig)
55
+ end
56
+
57
+ it 'formats the dates' do
58
+ BerkeleyLibrary::TIND::Config.timezone = tz_utc
59
+
60
+ date_range = DateRange.new(from_time: t1, until_time: t2)
61
+ params = date_range.to_params
62
+ expect(params[:d1]).to eq('2021-02-16 00:23:14')
63
+ expect(params[:d2]).to eq('2021-02-17 02:23:14')
64
+ end
65
+
66
+ it 'formats the dates in the configured timezone' do
67
+ BerkeleyLibrary::TIND::Config.timezone = tz_local
68
+
69
+ date_range = DateRange.new(from_time: t1, until_time: t2)
70
+ params = date_range.to_params
71
+ expect(params[:d1]).to eq('2021-02-16 13:23:14')
72
+ expect(params[:d2]).to eq('2021-02-17 15:23:14')
73
+ end
74
+ end
75
+
76
+ describe :ensure_date_range do
77
+ it 'accepts a DateRange' do
78
+ date_range = DateRange.new(from_time: t1, until_time: t2)
79
+ expect(DateRange.ensure_date_range(date_range)).to be(date_range)
80
+ end
81
+
82
+ it 'accepts a range of Dates' do
83
+ d1 = t1.to_date
84
+ d2 = t2.to_date
85
+ date_range = d1..d2
86
+ expected = DateRange.new(from_time: d1, until_time: d2)
87
+ expect(DateRange.ensure_date_range(date_range)).to eq(expected)
88
+ end
89
+
90
+ it 'accepts a range of times' do
91
+ time_range = t1..t2
92
+ expected = DateRange.new(from_time: t1, until_time: t2)
93
+ expect(DateRange.ensure_date_range(time_range)).to eq(expected)
94
+ end
95
+
96
+ it 'is always in UTC' do
97
+ time_range = t1..t2
98
+ date_range = DateRange.ensure_date_range(time_range)
99
+ expect(date_range.from_time).to eq(t1.getutc)
100
+ expect(date_range.until_time).to eq(t2.getutc)
101
+ end
102
+
103
+ it 'rejects things that are not date/time ranges' do
104
+ expect { DateRange.ensure_date_range('1975-1990') }.to raise_error(ArgumentError)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module BerkeleyLibrary
4
+ module TIND
5
+ module API
6
+ describe Format do
7
+ describe :to_s do
8
+ it 'returns the value' do
9
+ expected = { Format::ID => 'id', Format::XML => 'xml', Format::FILES => 'files', Format::JSON => 'json' }
10
+ expected.each do |fmt, val|
11
+ expect(fmt.value).to eq(val)
12
+ expect(fmt.to_s).to eq(val)
13
+ end
14
+ end
15
+ end
16
+
17
+ describe :to_str do
18
+ it 'returns the value' do
19
+ { Format::ID => 'id', Format::XML => 'xml', Format::FILES => 'files' }.each do |fmt, val|
20
+ expect(fmt.value).to eq(val)
21
+ expect(fmt.to_str).to eq(val)
22
+ end
23
+ end
24
+ end
25
+
26
+ describe :ensure_format do
27
+ it 'returns nil for nil' do
28
+ expect(Format.ensure_format(nil)).to be_nil
29
+ end
30
+
31
+ it 'returns a Format' do
32
+ expect(Format.ensure_format(Format::XML)).to be(Format::XML)
33
+ end
34
+
35
+ it 'accepts a string' do
36
+ expect(Format.ensure_format('xml')).to be(Format::XML)
37
+ end
38
+
39
+ it 'accepts a symbol' do
40
+ expect(Format.ensure_format(:xml)).to be(Format::XML)
41
+ end
42
+
43
+ it 'rejects unsupported values' do
44
+ expect { Format.ensure_format('CORBA') }.to raise_error(ArgumentError)
45
+ end
46
+
47
+ it 'rejects inconvertible values' do
48
+ expect { Format.ensure_format(Object.new) }.to raise_error(ArgumentError)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,364 @@
1
+ require 'spec_helper'
2
+ require 'webmock'
3
+
4
+ module BerkeleyLibrary
5
+ module TIND
6
+ module API
7
+ describe Search do
8
+ let(:base_uri) { 'https://tind.example.org/' }
9
+ let(:api_key) { 'lorem-ipsum-dolor-sit-amet' }
10
+ let(:query_uri) { BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/search') }
11
+ let(:headers) { { 'Authorization' => "Token #{api_key}" } }
12
+
13
+ before(:each) do
14
+ allow(BerkeleyLibrary::TIND::Config).to receive(:api_key).and_return(api_key)
15
+ allow(BerkeleyLibrary::TIND::Config).to receive(:base_uri).and_return(base_uri)
16
+ end
17
+
18
+ describe 'single page' do
19
+ let(:collection) { 'Bancroft Library' }
20
+ let(:search) { Search.new(collection: collection) }
21
+
22
+ before(:each) do
23
+ body = File.read('spec/data/records-api-search.xml')
24
+ stub_request(:get, query_uri)
25
+ .with(headers: headers, query: { 'c' => collection, 'format' => 'xml' })
26
+ .to_return(status: 200, body: body)
27
+ end
28
+
29
+ describe :results do
30
+ it 'returns the results' do
31
+ results = search.results
32
+ expect(results).to be_a(Array)
33
+ expect(results.size).to eq(5)
34
+ end
35
+ end
36
+
37
+ describe :each_result do
38
+ it 'iterates over the results' do
39
+ results = []
40
+ search.each_result { |r| results << r }
41
+ expect(results.size).to eq(5)
42
+ end
43
+
44
+ it 'returns an enumerator' do
45
+ results = []
46
+ enum = search.each_result
47
+ expect(enum).to be_a(Enumerable)
48
+ enum.each { |r| results << r }
49
+ expect(results.size).to eq(5)
50
+ end
51
+
52
+ describe :freeze do
53
+ describe 'with block' do
54
+ it 'defaults to false' do
55
+ search.each_result do |marc_record|
56
+ expect(marc_record).not_to be_frozen
57
+ end
58
+ end
59
+
60
+ it 'freezes results if true' do
61
+ search.each_result(freeze: true) do |marc_record|
62
+ expect(marc_record).to be_frozen
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'as enumerator' do
68
+ it 'defaults to false' do
69
+ enum = search.each_result
70
+ enum.each do |marc_record|
71
+ expect(marc_record).not_to be_frozen
72
+ end
73
+ end
74
+
75
+ it 'freezes results if true' do
76
+ enum = search.each_result(freeze: true)
77
+ enum.each do |marc_record|
78
+ expect(marc_record).to be_frozen
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ describe 'multiple pages' do
87
+ let(:collection) { 'Bancroft Library' }
88
+ let(:search) { Search.new(collection: collection) }
89
+ let(:search_id) { 'DnF1ZXJ5VGhlbkZldG' }
90
+ let(:expected_count) { 554 }
91
+
92
+ before(:each) do
93
+ result_xml_pages = (1..7).map { |page| File.read("spec/data/records-api-search-p#{page}.xml") }
94
+
95
+ query = { 'c' => collection, 'format' => 'xml' }
96
+
97
+ stub_request(:get, query_uri)
98
+ .with(headers: headers, query: query)
99
+ .to_return(status: 200, body: result_xml_pages[0])
100
+
101
+ stubs = result_xml_pages[1..].map { |b| { status: 200, body: b } }
102
+ stub_request(:get, query_uri).with(
103
+ headers: headers, query: query.merge({ 'search_id' => search_id })
104
+ ).to_return(stubs)
105
+ end
106
+
107
+ describe :results do
108
+ it 'returns the results' do
109
+ results = search.results
110
+ expect(results).to be_a(Array)
111
+ expect(results.size).to eq(expected_count)
112
+
113
+ first = results.first
114
+ expect(first).to be_a(::MARC::Record)
115
+ expect(first['024']['a']).to eq('BANC PIC 1958.021 Vol. 2:001--fALB')
116
+
117
+ last = results.last
118
+ expect(last).to be_a(::MARC::Record)
119
+ expect(last['024']['a']).to eq('BANC PIC 1958.021 Vol. 3:162--fALB')
120
+ end
121
+ end
122
+
123
+ describe :each_result do
124
+ it 'iterates over the results' do
125
+ results = []
126
+ search.each_result { |r| results << r }
127
+ expect(results.size).to eq(expected_count)
128
+ end
129
+
130
+ it 'returns an enumerator' do
131
+ results = []
132
+ enum = search.each_result
133
+ expect(enum).to be_a(Enumerable)
134
+ enum.each { |r| results << r }
135
+ expect(results.size).to eq(expected_count)
136
+ end
137
+
138
+ describe :freeze do
139
+ describe 'with block' do
140
+ it 'defaults to false' do
141
+ search.each_result do |marc_record|
142
+ expect(marc_record).not_to be_frozen
143
+ end
144
+ end
145
+
146
+ it 'freezes results if true' do
147
+ search.each_result(freeze: true) do |marc_record|
148
+ expect(marc_record).to be_frozen
149
+ end
150
+ end
151
+ end
152
+
153
+ describe 'as enumerator' do
154
+ it 'defaults to false' do
155
+ enum = search.each_result
156
+ enum.each do |marc_record|
157
+ expect(marc_record).not_to be_frozen
158
+ end
159
+ end
160
+
161
+ it 'freezes results if true' do
162
+ enum = search.each_result(freeze: true)
163
+ enum.each do |marc_record|
164
+ expect(marc_record).to be_frozen
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ describe 'error handling' do
173
+
174
+ describe 'Nonexistent collection' do
175
+ let(:collection) { 'Not a collection' }
176
+ let(:search) { Search.new(collection: collection) }
177
+ let(:body) { '{"success": false, "error": "need more than 0 values to unpack"}' }
178
+
179
+ before(:each) do
180
+ stub_request(:get, query_uri)
181
+ .with(headers: headers, query: { 'c' => collection, 'format' => 'xml' })
182
+ .to_return(status: 500, body: body, headers: { 'Content-Type' => 'applicaton/json' })
183
+ end
184
+
185
+ describe :results do
186
+ it 'returns an empty array' do
187
+ results = search.results
188
+ expect(results).to be_a(Array)
189
+ expect(results.size).to eq(0)
190
+ end
191
+ end
192
+
193
+ describe :each_result do
194
+ it 'yields nothing' do
195
+ expect { |b| search.each_result(&b) }.not_to yield_control
196
+ end
197
+ end
198
+ end
199
+
200
+ describe 'Empty result set' do
201
+ let(:collection) { 'Restricted2Bancroft' }
202
+ let(:search) { Search.new(collection: collection) }
203
+ let(:body) do
204
+ <<~XML
205
+ <response>
206
+ <total>0</total>
207
+ <search_id>DnF1ZXJ5VGhlbkZldGNoBQAAAAACe5PjFmN0YjJMb0tKUTQtV1VfVzI2Qm8yY1EAAAAAABFKdxZuTnljT0hTU1FrMi1QSkNwVUEtZHJRAAAAAAIsSOcWSC0wU1Y0N2pRWUdodVBEdmdZUjBGUQAAAAACBSBOFjN6NGdTWWgzVFNpMGdQaDJyeGxBUncAAAAABEoXGhZ3SkFfY25BaFIzYVJMOGlDWnhxbHJn</search_id>
208
+ <collection xmlns="http://www.loc.gov/MARC21/slim"/>
209
+ </response>
210
+ XML
211
+ end
212
+
213
+ before(:each) do
214
+ stub_request(:get, query_uri)
215
+ .with(headers: headers, query: { 'c' => collection, 'format' => 'xml' })
216
+ .to_return(status: 200, body: body, headers: { 'Content-Type' => 'applicaton/xml' })
217
+ end
218
+
219
+ describe :results do
220
+ it 'returns an empty array' do
221
+ results = search.results
222
+ expect(results).to be_a(Array)
223
+ expect(results.size).to eq(0)
224
+ end
225
+ end
226
+
227
+ describe :each_result do
228
+ it 'yields nothing' do
229
+ expect { |b| search.each_result(&b) }.not_to yield_control
230
+ end
231
+ end
232
+ end
233
+
234
+ describe 'Authn/Authz' do
235
+ let(:collection) { 'Bancroft Library' }
236
+ let(:search) { Search.new(collection: collection) }
237
+ let(:params) { { 'c' => collection, 'format' => 'xml' } }
238
+
239
+ describe 'insufficient privileges' do
240
+ # NOTE: TIND seems to return 'guest' regardless of username in the insufficient-privileges case
241
+ let(:body) { '{"error": "User guest is not authorized to perform runapi with parameters operation=read,endpoint=search"}' }
242
+
243
+ before(:each) do
244
+ @query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/search')
245
+
246
+ stub_request(:get, query_uri)
247
+ .with(headers: headers, query: params)
248
+ .to_return(status: 403, body: body)
249
+ end
250
+
251
+ it 'raises an APIException' do
252
+ expected_uri = BerkeleyLibrary::Util::URIs.append(query_uri, "?#{URI.encode_www_form(params)}")
253
+ expect { search.results }.to raise_error do |e|
254
+ expect(e).to be_a(APIException)
255
+ expect(e.message).to include('403 Forbidden')
256
+ expect(e.message).to include(expected_uri.to_s)
257
+ expect(e.status_code).to eq(403)
258
+ expect(e.status_message).to eq('403 Forbidden')
259
+ end
260
+ end
261
+ end
262
+
263
+ describe 'API key not set' do
264
+ it 'raises an APIException without actually performing the search' do
265
+ allow(BerkeleyLibrary::TIND::Config).to receive(:api_key).and_return(nil)
266
+
267
+ expected_uri = BerkeleyLibrary::Util::URIs.append(query_uri, "?#{URI.encode_www_form(params)}")
268
+ expect { search.results }.to raise_error do |e|
269
+ expect(e).to be_a(APIException)
270
+ expect(e.message).to include(expected_uri.to_s)
271
+ expect(e.status_code).to eq(401)
272
+ expect(e.status_message).to eq('401 Unauthorized')
273
+ end
274
+ end
275
+ end
276
+
277
+ describe 'invalid API key' do
278
+ let(:body) { '{"error": "The entered api key is invalid"}' }
279
+
280
+ before(:each) do
281
+ @query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/search')
282
+
283
+ stub_request(:get, query_uri)
284
+ .with(headers: headers, query: params)
285
+ .to_return(status: 401, body: body)
286
+ end
287
+
288
+ it 'raises an APIException' do
289
+ expected_uri = BerkeleyLibrary::Util::URIs.append(query_uri, "?#{URI.encode_www_form(params)}")
290
+ expect { search.results }.to raise_error do |e|
291
+ expect(e).to be_a(APIException)
292
+ expect(e.message).to include('401 Unauthorized')
293
+ expect(e.message).to include(expected_uri.to_s)
294
+ expect(e.status_code).to eq(401)
295
+ expect(e.status_message).to eq('401 Unauthorized')
296
+ end
297
+ end
298
+ end
299
+ end
300
+
301
+ describe '500 (some other error)' do
302
+ let(:collection) { 'Bancroft Library' }
303
+ let(:search) { Search.new(collection: collection) }
304
+ let(:params) { { 'c' => collection, 'format' => 'xml' } }
305
+
306
+ before(:each) do
307
+ @query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/search')
308
+
309
+ stub_request(:get, query_uri)
310
+ .with(headers: headers, query: params)
311
+ .to_return(status: 500, body: 'oops')
312
+ end
313
+
314
+ describe :results do
315
+ it 'raises an APIException' do
316
+ expected_uri = BerkeleyLibrary::Util::URIs.append(query_uri, "?#{URI.encode_www_form(params)}")
317
+
318
+ expect { search.results }.to raise_error do |e|
319
+ expect(e).to be_a(APIException)
320
+ expect(e.message).to include('500 Internal Server Error')
321
+ expect(e.message).to include(expected_uri.to_s)
322
+ expect(e.status_code).to eq(500)
323
+ expect(e.status_message).to eq('500 Internal Server Error')
324
+ end
325
+ end
326
+ end
327
+
328
+ describe :each_result do
329
+ it 'yields nothing' do
330
+ results = []
331
+ expect { search.each_result { |r| results << r } }.to raise_error do |e|
332
+ expect(e).to be_a(APIException)
333
+ expect(e.message).to include('500 Internal Server Error')
334
+ expect(e.message).to include(query_uri.to_s)
335
+ expect(e.status_code).to eq(500)
336
+ expect(e.status_message).to eq('500 Internal Server Error')
337
+ end
338
+ expect(results).to be_empty
339
+ end
340
+ end
341
+ end
342
+ end
343
+
344
+ it 'handles CJK' do
345
+ collection = 'Houcun ju shi ji'
346
+ search = Search.new(collection: collection)
347
+ params = { 'c' => collection, 'format' => 'xml' }
348
+
349
+ stub_request(:get, query_uri)
350
+ .with(headers: headers, query: params)
351
+ .to_return(status: 200, body: File.read('spec/data/records-api-search-cjk-p1.xml'))
352
+ stub_request(:get, query_uri)
353
+ .with(headers: headers, query: params.merge('search_id' => 'DnF1ZXJ5VGhlbkZldGNoBQAAAAAA'))
354
+ .to_return(status: 200, body: File.read('spec/data/records-api-search-cjk-p2.xml'))
355
+
356
+ results = search.results
357
+ expect(results).to be_a(Array)
358
+ expect(results.size).to eq(5)
359
+ end
360
+
361
+ end
362
+ end
363
+ end
364
+ end