berkeley_library-tind 0.4.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.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +18 -0
- data/.gitignore +388 -0
- data/.idea/inspectionProfiles/Project_Default.xml +20 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/tind.iml +138 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +334 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/.yardopts +1 -0
- data/CHANGES.md +58 -0
- data/Dockerfile +57 -0
- data/Gemfile +3 -0
- data/Jenkinsfile +18 -0
- data/LICENSE.md +21 -0
- data/README.md +73 -0
- data/Rakefile +20 -0
- data/berkeley_library-tind.gemspec +50 -0
- data/bin/tind-export +14 -0
- data/docker-compose.yml +15 -0
- data/lib/berkeley_library/tind.rb +3 -0
- data/lib/berkeley_library/tind/api.rb +1 -0
- data/lib/berkeley_library/tind/api/api.rb +132 -0
- data/lib/berkeley_library/tind/api/api_exception.rb +131 -0
- data/lib/berkeley_library/tind/api/collection.rb +82 -0
- data/lib/berkeley_library/tind/api/date_range.rb +67 -0
- data/lib/berkeley_library/tind/api/format.rb +32 -0
- data/lib/berkeley_library/tind/api/search.rb +100 -0
- data/lib/berkeley_library/tind/config.rb +103 -0
- data/lib/berkeley_library/tind/export.rb +1 -0
- data/lib/berkeley_library/tind/export/column.rb +54 -0
- data/lib/berkeley_library/tind/export/column_group.rb +144 -0
- data/lib/berkeley_library/tind/export/column_group_list.rb +131 -0
- data/lib/berkeley_library/tind/export/column_width_calculator.rb +76 -0
- data/lib/berkeley_library/tind/export/config.rb +154 -0
- data/lib/berkeley_library/tind/export/csv_exporter.rb +29 -0
- data/lib/berkeley_library/tind/export/export.rb +47 -0
- data/lib/berkeley_library/tind/export/export_command.rb +168 -0
- data/lib/berkeley_library/tind/export/export_exception.rb +8 -0
- data/lib/berkeley_library/tind/export/export_format.rb +67 -0
- data/lib/berkeley_library/tind/export/exporter.rb +105 -0
- data/lib/berkeley_library/tind/export/filter.rb +52 -0
- data/lib/berkeley_library/tind/export/no_results_error.rb +7 -0
- data/lib/berkeley_library/tind/export/ods_exporter.rb +138 -0
- data/lib/berkeley_library/tind/export/row.rb +24 -0
- data/lib/berkeley_library/tind/export/row_metrics.rb +18 -0
- data/lib/berkeley_library/tind/export/table.rb +175 -0
- data/lib/berkeley_library/tind/export/table_metrics.rb +116 -0
- data/lib/berkeley_library/tind/marc.rb +1 -0
- data/lib/berkeley_library/tind/marc/xml_reader.rb +144 -0
- data/lib/berkeley_library/tind/module_info.rb +14 -0
- data/lib/berkeley_library/util/arrays.rb +178 -0
- data/lib/berkeley_library/util/logging.rb +1 -0
- data/lib/berkeley_library/util/ods/spreadsheet.rb +170 -0
- data/lib/berkeley_library/util/ods/xml/content_doc.rb +26 -0
- data/lib/berkeley_library/util/ods/xml/document_node.rb +57 -0
- data/lib/berkeley_library/util/ods/xml/element_node.rb +106 -0
- data/lib/berkeley_library/util/ods/xml/loext/table_protection.rb +26 -0
- data/lib/berkeley_library/util/ods/xml/manifest/file_entry.rb +42 -0
- data/lib/berkeley_library/util/ods/xml/manifest/manifest.rb +73 -0
- data/lib/berkeley_library/util/ods/xml/manifest_doc.rb +26 -0
- data/lib/berkeley_library/util/ods/xml/namespace.rb +46 -0
- data/lib/berkeley_library/util/ods/xml/office/automatic_styles.rb +181 -0
- data/lib/berkeley_library/util/ods/xml/office/body.rb +17 -0
- data/lib/berkeley_library/util/ods/xml/office/document_content.rb +98 -0
- data/lib/berkeley_library/util/ods/xml/office/document_styles.rb +39 -0
- data/lib/berkeley_library/util/ods/xml/office/font_face_decls.rb +30 -0
- data/lib/berkeley_library/util/ods/xml/office/scripts.rb +17 -0
- data/lib/berkeley_library/util/ods/xml/office/spreadsheet.rb +37 -0
- data/lib/berkeley_library/util/ods/xml/office/styles.rb +39 -0
- data/lib/berkeley_library/util/ods/xml/style/cell_style.rb +58 -0
- data/lib/berkeley_library/util/ods/xml/style/column_style.rb +36 -0
- data/lib/berkeley_library/util/ods/xml/style/default_style.rb +31 -0
- data/lib/berkeley_library/util/ods/xml/style/family.rb +85 -0
- data/lib/berkeley_library/util/ods/xml/style/font_face.rb +46 -0
- data/lib/berkeley_library/util/ods/xml/style/paragraph_properties.rb +30 -0
- data/lib/berkeley_library/util/ods/xml/style/row_style.rb +37 -0
- data/lib/berkeley_library/util/ods/xml/style/style.rb +44 -0
- data/lib/berkeley_library/util/ods/xml/style/table_cell_properties.rb +40 -0
- data/lib/berkeley_library/util/ods/xml/style/table_column_properties.rb +30 -0
- data/lib/berkeley_library/util/ods/xml/style/table_properties.rb +25 -0
- data/lib/berkeley_library/util/ods/xml/style/table_row_properties.rb +28 -0
- data/lib/berkeley_library/util/ods/xml/style/table_style.rb +27 -0
- data/lib/berkeley_library/util/ods/xml/style/text_properties.rb +52 -0
- data/lib/berkeley_library/util/ods/xml/styles_doc.rb +26 -0
- data/lib/berkeley_library/util/ods/xml/table/named_expressions.rb +17 -0
- data/lib/berkeley_library/util/ods/xml/table/repeatable.rb +38 -0
- data/lib/berkeley_library/util/ods/xml/table/table.rb +193 -0
- data/lib/berkeley_library/util/ods/xml/table/table_cell.rb +46 -0
- data/lib/berkeley_library/util/ods/xml/table/table_column.rb +43 -0
- data/lib/berkeley_library/util/ods/xml/table/table_row.rb +136 -0
- data/lib/berkeley_library/util/ods/xml/text/p.rb +118 -0
- data/lib/berkeley_library/util/paths.rb +111 -0
- data/lib/berkeley_library/util/stringios.rb +30 -0
- data/lib/berkeley_library/util/strings.rb +42 -0
- data/lib/berkeley_library/util/sys_exits.rb +15 -0
- data/lib/berkeley_library/util/times.rb +22 -0
- data/lib/berkeley_library/util/uris.rb +44 -0
- data/lib/berkeley_library/util/uris/appender.rb +162 -0
- data/lib/berkeley_library/util/uris/requester.rb +62 -0
- data/lib/berkeley_library/util/uris/validator.rb +32 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +2 -0
- data/spec/.rubocop.yml +40 -0
- data/spec/berkeley_library/tind/api/api_exception_spec.rb +91 -0
- data/spec/berkeley_library/tind/api/api_spec.rb +143 -0
- data/spec/berkeley_library/tind/api/collection_spec.rb +74 -0
- data/spec/berkeley_library/tind/api/date_range_spec.rb +110 -0
- data/spec/berkeley_library/tind/api/format_spec.rb +54 -0
- data/spec/berkeley_library/tind/api/search_spec.rb +364 -0
- data/spec/berkeley_library/tind/config_spec.rb +86 -0
- data/spec/berkeley_library/tind/export/column_group_spec.rb +29 -0
- data/spec/berkeley_library/tind/export/column_spec.rb +43 -0
- data/spec/berkeley_library/tind/export/config_spec.rb +206 -0
- data/spec/berkeley_library/tind/export/export_command_spec.rb +169 -0
- data/spec/berkeley_library/tind/export/export_format_spec.rb +59 -0
- data/spec/berkeley_library/tind/export/export_matcher.rb +112 -0
- data/spec/berkeley_library/tind/export/export_spec.rb +150 -0
- data/spec/berkeley_library/tind/export/exporter_spec.rb +125 -0
- data/spec/berkeley_library/tind/export/row_spec.rb +118 -0
- data/spec/berkeley_library/tind/export/table_spec.rb +322 -0
- data/spec/berkeley_library/tind/marc/xml_reader_spec.rb +93 -0
- data/spec/berkeley_library/util/arrays_spec.rb +340 -0
- data/spec/berkeley_library/util/ods/spreadsheet_spec.rb +124 -0
- data/spec/berkeley_library/util/ods/xml/content_doc_spec.rb +121 -0
- data/spec/berkeley_library/util/ods/xml/manifest/file_entry_spec.rb +27 -0
- data/spec/berkeley_library/util/ods/xml/manifest/manifest_spec.rb +33 -0
- data/spec/berkeley_library/util/ods/xml/office/document_content_spec.rb +60 -0
- data/spec/berkeley_library/util/ods/xml/style/automatic_styles_spec.rb +37 -0
- data/spec/berkeley_library/util/ods/xml/style/family_spec.rb +57 -0
- data/spec/berkeley_library/util/ods/xml/table/table_row_spec.rb +179 -0
- data/spec/berkeley_library/util/ods/xml/table/table_spec.rb +218 -0
- data/spec/berkeley_library/util/paths_spec.rb +90 -0
- data/spec/berkeley_library/util/stringios_spec.rb +34 -0
- data/spec/berkeley_library/util/strings_spec.rb +27 -0
- data/spec/berkeley_library/util/times_spec.rb +39 -0
- data/spec/berkeley_library/util/uris_spec.rb +118 -0
- data/spec/data/collection-names.txt +438 -0
- data/spec/data/collections.json +4827 -0
- data/spec/data/disjoint-records.xml +187 -0
- data/spec/data/record-184453.xml +58 -0
- data/spec/data/record-184458.xml +63 -0
- data/spec/data/record-187888.xml +78 -0
- data/spec/data/records-api-search-cjk-p1.xml +6381 -0
- data/spec/data/records-api-search-cjk-p2.xml +5 -0
- data/spec/data/records-api-search-p1.xml +4506 -0
- data/spec/data/records-api-search-p2.xml +4509 -0
- data/spec/data/records-api-search-p3.xml +4506 -0
- data/spec/data/records-api-search-p4.xml +4509 -0
- data/spec/data/records-api-search-p5.xml +4506 -0
- data/spec/data/records-api-search-p6.xml +2436 -0
- data/spec/data/records-api-search-p7.xml +5 -0
- data/spec/data/records-api-search.xml +234 -0
- data/spec/data/records-manual-search.xml +547 -0
- data/spec/spec_helper.rb +30 -0
- data/test/profile/table_from_records_profile.rb +46 -0
- metadata +585 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
|
|
3
|
+
module BerkeleyLibrary
|
|
4
|
+
module Util
|
|
5
|
+
module URIs
|
|
6
|
+
module Validator
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
# Returns the specified URL as a URI.
|
|
10
|
+
# @param url [String, URI] the URL.
|
|
11
|
+
# @return [URI] the URI.
|
|
12
|
+
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
|
13
|
+
def uri_or_nil(url)
|
|
14
|
+
return unless url
|
|
15
|
+
|
|
16
|
+
# noinspection RubyYardReturnMatch
|
|
17
|
+
url.is_a?(URI) ? url : URI.parse(url.to_s)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns the specified URL as a string.
|
|
21
|
+
# @param url [String, URI] the URL.
|
|
22
|
+
# @return [String] the URL.
|
|
23
|
+
# @raise [URI::InvalidURIError] if `url` cannot be parsed as a URI.
|
|
24
|
+
def url_str_or_nil(url)
|
|
25
|
+
uri = Validator.uri_or_nil(url)
|
|
26
|
+
uri.to_s if uri
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/rakelib/bundle.rake
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'ci/reporter/rake/rspec'
|
|
2
|
+
|
|
3
|
+
# Configure CI::Reporter report generation
|
|
4
|
+
ENV['GENERATE_REPORTS'] ||= 'true'
|
|
5
|
+
ENV['CI_REPORTS'] = 'artifacts/rspec'
|
|
6
|
+
|
|
7
|
+
desc 'Run all specs in spec directory, with coverage'
|
|
8
|
+
task coverage: ['ci:setup:rspec'] do
|
|
9
|
+
ENV['COVERAGE'] ||= 'true'
|
|
10
|
+
Rake::Task[:spec].invoke
|
|
11
|
+
end
|
data/rakelib/gem.rake
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'rubygems/gem_runner'
|
|
2
|
+
require 'berkeley_library/tind/module_info'
|
|
3
|
+
|
|
4
|
+
gem_root_module = BerkeleyLibrary::TIND
|
|
5
|
+
|
|
6
|
+
class << gem_root_module
|
|
7
|
+
def project_root
|
|
8
|
+
@project_root ||= File.expand_path('..', __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def artifacts_dir
|
|
12
|
+
return project_root unless ENV['CI']
|
|
13
|
+
|
|
14
|
+
@artifacts_dir ||= File.join(project_root, 'artifacts')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def gemspec_file
|
|
18
|
+
@gemspec_file ||= begin
|
|
19
|
+
gemspec_files = Dir.glob(File.expand_path('*.gemspec', project_root))
|
|
20
|
+
raise ArgumentError, "Too many .gemspecs: #{gemspec_files.join(', ')}" if gemspec_files.size > 1
|
|
21
|
+
raise ArgumentError, 'No .gemspec file found' if gemspec_files.empty?
|
|
22
|
+
|
|
23
|
+
gemspec_files[0]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def gemspec_basename
|
|
28
|
+
File.basename(gemspec_file)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def output_file
|
|
32
|
+
@output_file ||= begin
|
|
33
|
+
gem_name = File.basename(gemspec_file, '.*')
|
|
34
|
+
version = self::ModuleInfo::VERSION
|
|
35
|
+
basename = "#{gem_name}-#{version}.gem"
|
|
36
|
+
File.join(artifacts_dir, basename)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def output_file_relative
|
|
41
|
+
return File.basename(output_file) unless ENV['CI']
|
|
42
|
+
|
|
43
|
+
@output_file_relative ||= begin
|
|
44
|
+
artifacts_dir_relative = File.basename(artifacts_dir)
|
|
45
|
+
File.join(artifacts_dir_relative, File.basename(output_file))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "Build #{gem_root_module.gemspec_basename} as #{gem_root_module.output_file_relative}"
|
|
51
|
+
task :gem do
|
|
52
|
+
args = ['build', gem_root_module.gemspec_file, "--output=#{gem_root_module.output_file}"]
|
|
53
|
+
Gem::GemRunner.new.run(args)
|
|
54
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'rubocop'
|
|
2
|
+
require 'rubocop/rake_task'
|
|
3
|
+
|
|
4
|
+
desc 'Run rubocop with HTML output'
|
|
5
|
+
RuboCop::RakeTask.new(:rubocop) do |cop|
|
|
6
|
+
output = ENV['RUBOCOP_OUTPUT'] || 'artifacts/rubocop/index.html'
|
|
7
|
+
puts "Writing RuboCop inspection report to #{output}"
|
|
8
|
+
|
|
9
|
+
cop.verbose = false
|
|
10
|
+
cop.formatters = ['html']
|
|
11
|
+
cop.options = ['--out', output]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'Run RuboCop with auto-correct, and output results to console'
|
|
15
|
+
task :ra do
|
|
16
|
+
# b/c we want console output, we can't just use `rubocop:auto_correct`
|
|
17
|
+
RuboCop::CLI.new.run(['--safe-auto-correct'])
|
|
18
|
+
end
|
data/rakelib/spec.rake
ADDED
data/spec/.rubocop.yml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
|
2
|
+
|
|
3
|
+
AllCops:
|
|
4
|
+
# Exclude generated files
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'suite/**/*'
|
|
7
|
+
|
|
8
|
+
Style/ClassAndModuleChildren:
|
|
9
|
+
Enabled: false
|
|
10
|
+
|
|
11
|
+
Style/MultilineBlockChain:
|
|
12
|
+
Enabled: false
|
|
13
|
+
|
|
14
|
+
Style/ParallelAssignment:
|
|
15
|
+
Enabled: false
|
|
16
|
+
|
|
17
|
+
Layout/LineLength:
|
|
18
|
+
Enabled: false
|
|
19
|
+
|
|
20
|
+
Metrics/AbcSize:
|
|
21
|
+
Enabled: false
|
|
22
|
+
|
|
23
|
+
Metrics/BlockLength:
|
|
24
|
+
Enabled: false
|
|
25
|
+
|
|
26
|
+
Metrics/ClassLength:
|
|
27
|
+
Enabled: false
|
|
28
|
+
|
|
29
|
+
Metrics/ModuleLength:
|
|
30
|
+
Enabled: false
|
|
31
|
+
|
|
32
|
+
Metrics/MethodLength:
|
|
33
|
+
Enabled: false
|
|
34
|
+
|
|
35
|
+
############################################################
|
|
36
|
+
# Added in Rubocop 0.89
|
|
37
|
+
|
|
38
|
+
# Sometimes we're testing the operator
|
|
39
|
+
Lint/BinaryOperatorWithIdenticalOperands:
|
|
40
|
+
Enabled: false
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'rest-client'
|
|
3
|
+
|
|
4
|
+
module BerkeleyLibrary
|
|
5
|
+
module TIND
|
|
6
|
+
module API
|
|
7
|
+
describe APIException do
|
|
8
|
+
describe :wrap do
|
|
9
|
+
it 'rejects nil' do
|
|
10
|
+
expect { APIException.wrap(nil) }.to raise_error(ArgumentError)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe 'RestClient::RequestFailed' do
|
|
14
|
+
it 'extracts the status info and response' do
|
|
15
|
+
url = 'https://example.org'
|
|
16
|
+
params = { 'foo' => 'bar' }
|
|
17
|
+
expected_body = 'oops'
|
|
18
|
+
stub_request(:get, url).with(query: params).to_return(status: 500, body: expected_body)
|
|
19
|
+
|
|
20
|
+
expected_msg = 'the wrapper message'
|
|
21
|
+
expect do
|
|
22
|
+
RestClient.get(url, params: params)
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
raise APIException.wrap(e, url: url, params: params, detail: expected_msg)
|
|
25
|
+
end.to raise_error do |e|
|
|
26
|
+
expect(e).to be_a(APIException)
|
|
27
|
+
expect(e.cause).to be_a(RestClient::RequestFailed)
|
|
28
|
+
expect(e.status_code).to eq(500)
|
|
29
|
+
expect(e.status_message).to eq('500 Internal Server Error')
|
|
30
|
+
expect(e.body).to eq(expected_body)
|
|
31
|
+
expect(e.response).to be(e.cause.response)
|
|
32
|
+
expect(e.url).to eq(url)
|
|
33
|
+
expect(e.params).to eq(params)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'works with or without parameters' do
|
|
38
|
+
url = 'https://example.org'
|
|
39
|
+
expected_body = 'oops'
|
|
40
|
+
stub_request(:get, url).to_return(status: 500, body: expected_body)
|
|
41
|
+
|
|
42
|
+
expected_msg = 'the wrapper message'
|
|
43
|
+
expect do
|
|
44
|
+
RestClient.get(url)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
raise APIException.wrap(e, url: url, detail: expected_msg)
|
|
47
|
+
end.to raise_error do |e|
|
|
48
|
+
expect(e).to be_a(APIException)
|
|
49
|
+
expect(e.cause).to be_a(RestClient::RequestFailed)
|
|
50
|
+
expect(e.status_code).to eq(500)
|
|
51
|
+
expect(e.status_message).to eq('500 Internal Server Error')
|
|
52
|
+
expect(e.body).to eq(expected_body)
|
|
53
|
+
expect(e.response).to be(e.cause.response)
|
|
54
|
+
expect(e.url).to eq(url)
|
|
55
|
+
expect(e.params).to be_nil
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe 'any exception' do
|
|
61
|
+
it 'wraps an exception' do
|
|
62
|
+
expected_msg = 'the message'
|
|
63
|
+
expect do
|
|
64
|
+
|
|
65
|
+
raise expected_msg
|
|
66
|
+
rescue StandardError => e
|
|
67
|
+
raise APIException.wrap(e)
|
|
68
|
+
end.to raise_error do |e|
|
|
69
|
+
expect(e).to be_a(APIException)
|
|
70
|
+
expect(e.message).to eq(expected_msg)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'overrides the message' do
|
|
75
|
+
expected_msg = 'the message'
|
|
76
|
+
expect do
|
|
77
|
+
|
|
78
|
+
raise "not #{expected_msg}"
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
raise APIException.wrap(e, msg: expected_msg)
|
|
81
|
+
end.to raise_error do |e|
|
|
82
|
+
expect(e).to be_a(APIException)
|
|
83
|
+
expect(e.message).to eq(expected_msg)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'webmock'
|
|
3
|
+
|
|
4
|
+
module BerkeleyLibrary
|
|
5
|
+
module TIND
|
|
6
|
+
RSpec.shared_examples 'a missing key' do |bad_key|
|
|
7
|
+
before(:each) do
|
|
8
|
+
expect(BerkeleyLibrary::TIND::Config).to receive(:api_key).and_return(bad_key)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "raises #{API::APIKeyNotSet}" do
|
|
12
|
+
failure_message = -> { "#{API::APIKeyNotSet} not raised for API key #{bad_key.inspect}" }
|
|
13
|
+
expect { API.get('some-endpoint') }.to raise_error(API::APIKeyNotSet), failure_message
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
RSpec.shared_examples 'a missing base URL' do |bad_url|
|
|
18
|
+
before(:each) do
|
|
19
|
+
allow(BerkeleyLibrary::TIND::Config).to receive(:base_uri).and_return(bad_url)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "raises #{API::APIKeyNotSet}" do
|
|
23
|
+
failure_message = -> { "#{API::BaseURINotSet} not raised for API key #{bad_url.inspect}" }
|
|
24
|
+
expect { API.get('some-endpoint') }.to raise_error(API::BaseURINotSet), failure_message
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
describe API do
|
|
29
|
+
let(:base_uri) { 'https://tind.example.org/' }
|
|
30
|
+
let(:api_key) { 'lorem-ipsum-dolor-sit-amet' }
|
|
31
|
+
|
|
32
|
+
describe :get do
|
|
33
|
+
describe 'with invalid API key' do
|
|
34
|
+
before(:each) do
|
|
35
|
+
allow(BerkeleyLibrary::TIND::Config).to receive(:base_uri).and_return(base_uri)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
[nil, '', ' '].each do |bad_key|
|
|
39
|
+
describe "api_key = #{bad_key.inspect}" do
|
|
40
|
+
it_behaves_like 'a missing key', bad_key
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe 'bad TIND base URI' do
|
|
47
|
+
before(:each) do
|
|
48
|
+
allow(BerkeleyLibrary::TIND::Config).to receive(:api_key).and_return(api_key)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
[nil, '', ' '].each do |bad_url|
|
|
52
|
+
describe "base_uri = #{bad_url.inspect}" do
|
|
53
|
+
it_behaves_like 'a missing base URL', bad_url
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe 'with valid config' do
|
|
59
|
+
before(:each) do
|
|
60
|
+
allow(BerkeleyLibrary::TIND::Config).to receive(:base_uri).and_return(base_uri)
|
|
61
|
+
allow(BerkeleyLibrary::TIND::Config).to receive(:api_key).and_return(api_key)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe :get do
|
|
65
|
+
it "raises #{API::APIException} in the event of an invalid response" do
|
|
66
|
+
aggregate_failures 'responses' do
|
|
67
|
+
[207, 400, 401, 403, 404, 405, 418, 451, 500, 503].each do |code|
|
|
68
|
+
endpoint = "endpoint-#{code}"
|
|
69
|
+
url_str = API.uri_for(endpoint).to_s
|
|
70
|
+
stub_request(:get, url_str).to_return(status: code)
|
|
71
|
+
|
|
72
|
+
expect { API.get(endpoint) }.to raise_error(API::APIException) do |e|
|
|
73
|
+
expect(e.message).to include(code.to_s)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'logs the response body if an error occurs in handling' do
|
|
80
|
+
endpoint = 'test-endpoint'
|
|
81
|
+
url_str = API.uri_for(endpoint).to_s
|
|
82
|
+
body_text = 'the body'
|
|
83
|
+
stub_request(:get, url_str).to_return(status: 200, body: body_text)
|
|
84
|
+
|
|
85
|
+
logdev = StringIO.new
|
|
86
|
+
logger = BerkeleyLibrary::Logging::Loggers.new_readable_logger(logdev)
|
|
87
|
+
allow(BerkeleyLibrary::Logging).to receive(:logger).and_return(logger)
|
|
88
|
+
|
|
89
|
+
msg = 'the error message'
|
|
90
|
+
expect { API.get(endpoint) { |_| raise(StandardError, msg) } }.to raise_error(StandardError, msg)
|
|
91
|
+
expect(logdev.string).to include(body_text)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'sends a default user agent' do
|
|
95
|
+
expected_ua = Config::DEFAULT_USER_AGENT
|
|
96
|
+
|
|
97
|
+
endpoint = 'test-endpoint'
|
|
98
|
+
url_str = API.uri_for(endpoint).to_s
|
|
99
|
+
body_text = 'the body'
|
|
100
|
+
stub_request(:get, url_str).with(headers: { 'User-Agent' => expected_ua })
|
|
101
|
+
.to_return(status: 200, body: body_text)
|
|
102
|
+
|
|
103
|
+
result = API.get(endpoint)
|
|
104
|
+
expect(result).to eq(body_text)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'sends a configured user ageint' do
|
|
108
|
+
expected_ua = 'Help I am trapped in an HTTP request'
|
|
109
|
+
Config.user_agent = expected_ua
|
|
110
|
+
|
|
111
|
+
endpoint = 'test-endpoint'
|
|
112
|
+
url_str = API.uri_for(endpoint).to_s
|
|
113
|
+
body_text = 'the body'
|
|
114
|
+
stub_request(:get, url_str).with(headers: { 'User-Agent' => expected_ua })
|
|
115
|
+
.to_return(status: 200, body: body_text)
|
|
116
|
+
|
|
117
|
+
result = API.get(endpoint)
|
|
118
|
+
expect(result).to eq(body_text)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe :format_request do
|
|
124
|
+
it 'formats a request' do
|
|
125
|
+
url = 'https://example.org/frob'
|
|
126
|
+
params = { foo: 'bar', 'qux' => 'baz' }
|
|
127
|
+
expected_url = 'https://example.org/frob?foo=bar&qux=baz'
|
|
128
|
+
expect(API.format_request(url, params)).to eq("GET #{expected_url}")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'works without parameters' do
|
|
132
|
+
url = 'https://example.org/frob'
|
|
133
|
+
expect(API.format_request(url)).to eq("GET #{url}")
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'rejects garbage parameters' do
|
|
137
|
+
# noinspection RubyYardParamTypeMatch
|
|
138
|
+
expect { API.format_request('https://example.org', Object.new) }.to raise_error(ArgumentError)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'webmock'
|
|
3
|
+
|
|
4
|
+
module BerkeleyLibrary
|
|
5
|
+
module TIND
|
|
6
|
+
module API
|
|
7
|
+
describe Collection do
|
|
8
|
+
let(:base_uri) { 'https://tind.example.org/' }
|
|
9
|
+
let(:api_key) { 'not-a-real-api-key' }
|
|
10
|
+
|
|
11
|
+
before(:each) do
|
|
12
|
+
@base_uri_orig = BerkeleyLibrary::TIND::Config.instance_variable_get(:@base_uri)
|
|
13
|
+
BerkeleyLibrary::TIND::Config.base_uri = base_uri
|
|
14
|
+
|
|
15
|
+
@api_key_orig = BerkeleyLibrary::TIND::Config.instance_variable_get(:@api_key)
|
|
16
|
+
BerkeleyLibrary::TIND::Config.api_key = api_key
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after(:each) do
|
|
20
|
+
BerkeleyLibrary::TIND::Config.instance_variable_set(:@base_uri, @base_uri_orig)
|
|
21
|
+
BerkeleyLibrary::TIND::Config.instance_variable_set(:@api_key, @api_key_orig)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe :all do
|
|
25
|
+
it 'reads the collections from the API' do
|
|
26
|
+
query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/collections?depth=100')
|
|
27
|
+
stub_request(:get, query_uri)
|
|
28
|
+
.with(headers: { 'Authorization' => 'Token not-a-real-api-key' })
|
|
29
|
+
.to_return(status: 200, body: File.read('spec/data/collections.json'))
|
|
30
|
+
|
|
31
|
+
all_collections = Collection.all
|
|
32
|
+
expect(all_collections.size).to eq(1)
|
|
33
|
+
expect(root_collection = all_collections.first).not_to be_nil
|
|
34
|
+
expect(root_collection.name).to eq('Digital Collections')
|
|
35
|
+
|
|
36
|
+
top_level_collections = root_collection.children
|
|
37
|
+
expect(top_level_collections.map(&:name)).to eq(['Bampfa', 'Bancroft Library', 'Berkeley Library', 'East Asian Library', 'Humanities & Social Sciences', 'Sciences'])
|
|
38
|
+
|
|
39
|
+
bancroft = top_level_collections.find { |c| c.name == 'Bancroft Library' }
|
|
40
|
+
examiner = bancroft.children.find { |c| c.name == 'SFExaminer' }
|
|
41
|
+
expect(examiner.name_en).to eq('San Francisco Examiner')
|
|
42
|
+
expect(examiner.size).to eq(15_564)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'returns an empty array in the event of an error' do
|
|
46
|
+
query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/collections?depth=100')
|
|
47
|
+
stub_request(:get, query_uri).to_return(status: 404)
|
|
48
|
+
|
|
49
|
+
expect(Collection.all).to eq([])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'raises an error if the API key is not set' do
|
|
53
|
+
BerkeleyLibrary::TIND::API.instance_variable_set(:@api_key, nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe :each_collection do
|
|
58
|
+
it 'yields each collection' do
|
|
59
|
+
collections_json = File.read('spec/data/collections.json')
|
|
60
|
+
expected_names = File.readlines('spec/data/collection-names.txt', chomp: true)
|
|
61
|
+
|
|
62
|
+
query_uri = BerkeleyLibrary::Util::URIs.append(base_uri, '/api/v1/collections?depth=100')
|
|
63
|
+
stub_request(:get, query_uri)
|
|
64
|
+
.with(headers: { 'Authorization' => 'Token not-a-real-api-key' })
|
|
65
|
+
.to_return(status: 200, body: collections_json)
|
|
66
|
+
|
|
67
|
+
actual_names = Collection.each_collection.each_with_object([]) { |c, arr| arr << c.name }
|
|
68
|
+
expect(actual_names).to contain_exactly(*expected_names)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|