qa 5.13.0 → 5.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c2bc5171a4ed527a81eb174b2e5e7f0e99d86c2df285c2619d415ec4587af84
4
- data.tar.gz: 8fd957474f3f185867025fb18e08d4262e207ae051bca57bc43eaac10bc7b097
3
+ metadata.gz: 11cbb13006d50dd1457c8782b84f49a677717302d43a3978e3629670ac9ec433
4
+ data.tar.gz: 72c2de2f8114cc9df223ef68eeac9f3ff9046a74c87dd93103a3461d24c2c15e
5
5
  SHA512:
6
- metadata.gz: 54cc9b6d0289157de648f34c040355eb06f206ca0d616664d96e6ae3d3e5e754e950c447127d61cf16f6d3f15f874fd65d23845d1f912ce89c3e1571875a9d8e
7
- data.tar.gz: 44e459c8a7424a8116f075979fa7ce2023b5307edb48c2a044cfdea5e5239435b8a85375687a9f2a51b7c63af60e2435bf27769043c9fa8be1de9d0f98c8fe9e
6
+ metadata.gz: 30f799610d3d6980f0e33d3b5fcb2364e9c0019c18f8a12f5474dd22a82ccb865f95cc5f4d49da70df2246fda033ab17ea20dd3da554c220b881841d2a64c1bf
7
+ data.tar.gz: cc3c22f2e7cb940eff5f7f3d398090ae2caaff78840a81eff1ea9e229d1db2c2a17673617e0cd12fd9aef1a6ffa30128accf66cf8eaa1537b0df901e1847ceac
@@ -42,7 +42,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
42
42
  # get "/search/linked_data/:vocab(/:subauthority)"
43
43
  # @see Qa::Authorities::LinkedData::SearchQuery#search
44
44
  def search # rubocop:disable Metrics/MethodLength
45
- terms = @authority.search(query, request_header: request_header_service.search_header)
45
+ terms = @authority.search(query)
46
46
  cors_allow_origin_header(response)
47
47
  render json: terms
48
48
  rescue Qa::ServiceUnavailable
@@ -65,7 +65,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
65
65
  # get "/show/linked_data/:vocab/:subauthority/:id
66
66
  # @see Qa::Authorities::LinkedData::FindTerm#find
67
67
  def show # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
68
- term = @authority.find(id, request_header: request_header_service.fetch_header)
68
+ term = @authority.find(id)
69
69
  cors_allow_origin_header(response)
70
70
  render json: term, content_type: request_header_service.content_type_for_format
71
71
  rescue Qa::TermNotFound
@@ -95,7 +95,7 @@ class Qa::LinkedDataTermsController < ::ApplicationController
95
95
  # get "/fetch/linked_data/:vocab"
96
96
  # @see Qa::Authorities::LinkedData::FindTerm#find
97
97
  def fetch # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
98
- term = @authority.find(uri, request_header: request_header_service.fetch_header)
98
+ term = @authority.find(uri)
99
99
  cors_allow_origin_header(response)
100
100
  render json: term, content_type: request_header_service.content_type_for_format
101
101
  rescue Qa::TermNotFound
@@ -157,9 +157,14 @@ class Qa::LinkedDataTermsController < ::ApplicationController
157
157
  @request_header_service = request_header_service_class.new(request: request, params: params)
158
158
  end
159
159
 
160
+ # @see Qa::AuthorityWrapper for these methods
161
+ delegate :search_header, :fetch_header, to: :request_header_service
162
+
160
163
  def init_authority
161
- @authority = Qa::Authorities::LinkedData::GenericAuthority.new(vocab_param)
162
- rescue Qa::InvalidLinkedDataAuthority => e
164
+ @authority = Qa.authority_for(vocab: params[:vocab],
165
+ subauthority: params[:subauthority],
166
+ context: self)
167
+ rescue Qa::InvalidAuthorityError, Qa::InvalidLinkedDataAuthority => e
163
168
  msg = e.message
164
169
  logger.warn msg
165
170
  render json: { errors: msg }, status: :bad_request
@@ -69,26 +69,15 @@ class Qa::TermsController < ::ApplicationController
69
69
  end
70
70
 
71
71
  def init_authority # rubocop:disable Metrics/MethodLength
72
- begin
73
- mod = authority_class.camelize.constantize
74
- rescue NameError
75
- msg = "Unable to initialize authority #{authority_class}"
76
- logger.warn msg
77
- render json: { errors: msg }, status: :bad_request
78
- return
79
- end
80
- begin
81
- @authority = if mod.is_a? Class
82
- mod.new
83
- else
84
- raise Qa::MissingSubAuthority, "No sub-authority provided" if params[:subauthority].blank?
85
- mod.subauthority_for(params[:subauthority])
86
- end
87
- rescue Qa::InvalidSubAuthority, Qa::MissingSubAuthority => e
88
- msg = e.message
89
- logger.warn msg
90
- render json: { errors: msg }, status: :bad_request
91
- end
72
+ @authority = Qa.authority_for(vocab: params[:vocab],
73
+ subauthority: params[:subauthority],
74
+ # Included to preserve error message text
75
+ try_linked_data_config: false,
76
+ context: self)
77
+ rescue Qa::InvalidAuthorityError, Qa::InvalidSubAuthority, Qa::MissingSubAuthority => e
78
+ msg = e.message
79
+ logger.warn msg
80
+ render json: { errors: msg }, status: :bad_request
92
81
  end
93
82
 
94
83
  def check_query_param
@@ -0,0 +1,6 @@
1
+ {
2
+ "search": {
3
+ "urls" : { "fastsuggest": "https://fast.oclc.org/searchfast/fastsuggest" },
4
+ "connection": { "timeout":"5"}
5
+ }
6
+ }
@@ -19,6 +19,7 @@ module Qa::Authorities
19
19
  Faraday.get(url) do |req|
20
20
  req.options.params_encoder = space_fix_encoder
21
21
  req.headers['Accept'] = 'application/json'
22
+ req.options.timeout = connection_timeout_in_seconds unless connection_timeout_in_seconds.nil?
22
23
  end
23
24
  end
24
25
 
@@ -49,11 +50,25 @@ module Qa::Authorities
49
50
 
50
51
  # sort=usage+desc is not documented by OCLC but seems necessary to get the sort
51
52
  # we formerly got without specifying, that is most useful in our use case.
52
- "http://fast.oclc.org/searchfast/fastsuggest?&query=#{escaped_query}&queryIndex=#{index}&queryReturn=#{return_data}&suggest=autoSubject&rows=#{num_rows}&sort=usage+desc"
53
+ "#{assign_fast_url}?&query=#{escaped_query}&queryIndex=#{index}&queryReturn=#{return_data}&suggest=autoSubject&rows=#{num_rows}&sort=usage+desc"
53
54
  end
54
55
 
55
56
  private
56
57
 
58
+ # See https://github.com/samvera/questioning_authority/wiki/Connecting-to-OCLC-FAST
59
+ # for more info about config settings for this authority.
60
+ def assign_fast_config
61
+ Qa.config.assign_fast_authority_configs
62
+ end
63
+
64
+ def connection_timeout_in_seconds
65
+ assign_fast_config.dig(:OCLC_ASSIGN_FAST, :search, :connection, :timeout)&.to_i
66
+ end
67
+
68
+ def assign_fast_url
69
+ assign_fast_config.dig(:OCLC_ASSIGN_FAST, :search, :urls, :fastsuggest) || 'https://fast.oclc.org/searchfast/fastsuggest'
70
+ end
71
+
57
72
  # Removes characters from the query string that are not tolerated by the API
58
73
  # See oclc sample code at
59
74
  # http://experimental.worldcat.org/fast/assignfast/js/assignFASTComplete.js
@@ -29,5 +29,8 @@ module Qa::Authorities
29
29
  def find(_id)
30
30
  raise NotImplementedError, "#{self.class}#find is unimplemented."
31
31
  end
32
+
33
+ class_attribute :linked_data, instance_writer: false
34
+ self.linked_data = false
32
35
  end
33
36
  end
@@ -13,6 +13,8 @@ module Qa::Authorities
13
13
  attr_reader :authority_config
14
14
  private :authority_config
15
15
 
16
+ self.linked_data = true
17
+
16
18
  delegate :supports_term?, :term_subauthorities?, :term_subauthority?,
17
19
  :term_id_expects_uri?, :term_id_expects_id?, to: :term_config
18
20
 
@@ -35,7 +35,8 @@ module Qa::Authorities
35
35
  end
36
36
 
37
37
  def find_url(id)
38
- "https://id.loc.gov/authorities/#{@subauthority}/#{id}.json"
38
+ root_fetch_slug = Loc.root_fetch_slug_for(@subauthority)
39
+ File.join("https://id.loc.gov/", root_fetch_slug, "/#{@subauthority}/#{id}.json")
39
40
  end
40
41
 
41
42
  private
@@ -1,4 +1,5 @@
1
1
  module Qa::Authorities::LocSubauthority
2
+ # @todo Rename to reflect that this is a URI encoded url fragement used only for searching.
2
3
  def get_url_for_authority(authority)
3
4
  if authorities.include?(authority) then authority_base_url
4
5
  elsif vocabularies.include?(authority) then vocab_base_url
@@ -7,6 +8,23 @@ module Qa::Authorities::LocSubauthority
7
8
  end
8
9
  end
9
10
 
11
+ # @note The returned value is the root directory of the URL. The graphicMaterials sub-authority
12
+ # has a "type" of vocabulary. https://id.loc.gov/vocabulary/graphicMaterials/tgm008083.html
13
+ # In some cases, this is plural and in others this is singular.
14
+ #
15
+ # @param authority [String] the LOC authority that matches one of the types
16
+ # @return [String]
17
+ #
18
+ # @note there is a relationship between the returned value and the encoded URLs returned by
19
+ # {#get_url_for_authority}.
20
+ def root_fetch_slug_for(authority)
21
+ validate_subauthority!(authority)
22
+ return "authorities" if authorities.include?(authority)
23
+ return "vocabulary" if vocabularies.include?(authority)
24
+ return "datatype" if datatypes.include?(authority)
25
+ return "vocabulary/preservation" if preservation.include?(authority)
26
+ end
27
+
10
28
  def authorities
11
29
  [
12
30
  "subjects",
@@ -0,0 +1,49 @@
1
+ module Qa
2
+ # @note THIS IS NOT TESTED NOR EXERCISED CODE IT IS PROVIDED AS CONJECTURE. FUTURE CHANGES MIGHT
3
+ # BUILD AND REFACTOR UPON THIS.
4
+ #
5
+ # @api private
6
+ # @abstract
7
+ #
8
+ # This class is responsible for exposing methods that are required by both linked data and
9
+ # non-linked data authorities. As of v5.10.0, those three methods are: params, search_header,
10
+ # fetch_header. Those are the methods that are used in {Qa::LinkedData::RequestHeaderService} and
11
+ # in {Qa::Authorities::Discogs::GenericAuthority}.
12
+ #
13
+ # The intention is to provide a class that can behave like a controller object without being that
14
+ # entire controller object.
15
+ #
16
+ # @see Qa::LinkedData::RequestHeaderService
17
+ # @see Qa::Authorities::Discogs::GenericAuthority
18
+ class AuthorityRequestContext
19
+ def self.fallback
20
+ new
21
+ end
22
+
23
+ def initialize(params: {}, headers: {}, **kwargs)
24
+ @params = params
25
+ @headers = headers
26
+ (SEARCH_HEADER_KEYS + FETCH_HEADER_KEYS).uniq.each do |key|
27
+ send("#{key}=", kwargs[key]) if kwargs.key?(key)
28
+ end
29
+ end
30
+
31
+ SEARCH_HEADER_KEYS = %i[request request_id subauthority user_language performance_data context response_header replacements].freeze
32
+ FETCH_HEADER_KEYS = %i[request request_id subauthority user_language performance_data format response_header replacements].freeze
33
+
34
+ attr_accessor :params, :headers
35
+ attr_accessor(*(SEARCH_HEADER_KEYS + FETCH_HEADER_KEYS).uniq)
36
+
37
+ def search_header
38
+ SEARCH_HEADER_KEYS.each_with_object(headers.deep_dup) do |key, header|
39
+ header[key] = send(key) if send(key).present?
40
+ end.compact
41
+ end
42
+
43
+ def fetch_header
44
+ FETCH_HEADER_KEYS.each_with_object(headers.deep_dup) do |key, header|
45
+ header[key] = send(key) if send(key).present?
46
+ end.compact
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,63 @@
1
+ module Qa
2
+ # @api public
3
+ # @since v5.11.0
4
+ #
5
+ # The intention of this wrapper is to provide a common interface that both linked and non-linked
6
+ # data can use. There are implementation differences between the two, but with this wrapper, the
7
+ # goal is to draw attention to those differences and insulate the end user from those issues.
8
+ #
9
+ # One benefit in introducing this class is that when interacting with a questioning authority
10
+ # implementation you don't need to consider "Hey when I instantiate an authority, is this linked
11
+ # data or not?" And what specifically are the parameter differences. You will need to perhaps
12
+ # include some additional values in the context if you don't call this from a controller.
13
+ class AuthorityWrapper
14
+ require 'qa/authority_request_context.rb'
15
+ # @param authority [#find, #search]
16
+ # @param subauthority [#to_s]
17
+ # @param context [#params, #search_header, #fetch_header]
18
+ def initialize(authority:, subauthority:, context:)
19
+ @authority = authority
20
+ @subauthority = subauthority
21
+ @context = context
22
+ configure!
23
+ end
24
+ attr_reader :authority, :context, :subauthority
25
+
26
+ def search(value)
27
+ if linked_data?
28
+ # should respond to search_header
29
+ authority.search(value, request_header: context.search_header)
30
+ elsif authority.method(:search).arity == 2
31
+ # This context should respond to params; see lib/qa/authorities/discogs/generic_authority.rb
32
+ authority.search(value, context)
33
+ else
34
+ authority.search(value)
35
+ end
36
+ end
37
+
38
+ # context has params
39
+ def find(value)
40
+ if linked_data?
41
+ # should respond to fetch_header
42
+ authority.find(value, request_header: context.fetch_header)
43
+ elsif authority.method(:find).arity == 2
44
+ authority.find(value, context)
45
+ else
46
+ authority.find(value)
47
+ end
48
+ end
49
+ alias fetch find
50
+
51
+ def method_missing(method_name, *arguments, &block)
52
+ authority.send(method_name, *arguments, &block)
53
+ end
54
+
55
+ def respond_to_missing?(method_name, include_private = false)
56
+ authority.respond_to?(method_name, include_private)
57
+ end
58
+
59
+ def configure!
60
+ @context.subauthority = @subauthority if @context.respond_to?(:subauthority)
61
+ end
62
+ end
63
+ end
@@ -28,8 +28,8 @@ module Qa
28
28
  token == authorized_reload_token
29
29
  end
30
30
 
31
- # Hold linked data authority configs
32
- attr_accessor :linked_data_authority_configs
31
+ # Hold authority configs
32
+ attr_accessor :linked_data_authority_configs, :assign_fast_authority_configs
33
33
 
34
34
  # For linked data access, specify default language for sorting and selection. The default is only used if a language is not
35
35
  # specified in the authority's configuration file and not passed in as a parameter. (e.g. :en, [:en], or [:en, :fr])
@@ -2,25 +2,45 @@
2
2
  module Qa
3
3
  module LinkedData
4
4
  class AuthorityService
5
- # Load or reload the linked data configuration files
6
5
  def self.load_authorities
7
- auth_cfg = {}
8
- # load QA configured linked data authorities
6
+ load_linked_data_config
7
+ load_assign_fast_config
8
+ end
9
+
10
+ # Load or reload the linked data configuration files
11
+ def self.load_linked_data_config
12
+ ld_auth_cfg = {}
13
+ # Linked data settings
9
14
  Dir[File.join(Qa::Engine.root, 'config', 'authorities', 'linked_data', '*.json')].each do |fn|
10
- auth = File.basename(fn, '.json').upcase.to_sym
11
- json = File.read(File.expand_path(fn, __FILE__))
12
- cfg = JSON.parse(json).deep_symbolize_keys
13
- auth_cfg[auth] = cfg
15
+ process_config_file(file_path: fn, config_hash: ld_auth_cfg)
14
16
  end
15
-
16
- # load app configured linked data authorities and overrides
17
+ # Optional local (app) linked data settings overrides
17
18
  Dir[Rails.root.join('config', 'authorities', 'linked_data', '*.json')].each do |fn|
18
- auth = File.basename(fn, '.json').upcase.to_sym
19
- json = File.read(File.expand_path(fn, __FILE__))
20
- cfg = JSON.parse(json).deep_symbolize_keys
21
- auth_cfg[auth] = cfg
19
+ process_config_file(file_path: fn, config_hash: ld_auth_cfg)
22
20
  end
23
- Qa.config.linked_data_authority_configs = auth_cfg
21
+ Qa.config.linked_data_authority_configs = ld_auth_cfg
22
+ end
23
+
24
+ # similar to the above; these settings are for getting (non-linked-data) FAST subject headings from OCLC.
25
+ def self.load_assign_fast_config
26
+ assign_fast_auth_cfg = {}
27
+ # assign_fast settings
28
+ Dir[File.join(Qa::Engine.root, 'config', 'authorities', 'assign_fast', '*.json')].each do |fn|
29
+ process_config_file(file_path: fn, config_hash: assign_fast_auth_cfg)
30
+ end
31
+ # Optional local (app) assign_fast settings overrides
32
+ Dir[Rails.root.join('config', 'authorities', 'assign_fast', '*.json')].each do |fn|
33
+ process_config_file(file_path: fn, config_hash: assign_fast_auth_cfg)
34
+ end
35
+ Qa.config.assign_fast_authority_configs = assign_fast_auth_cfg
36
+ end
37
+
38
+ # load settings into a configuration hash:
39
+ def self.process_config_file(file_path:, config_hash:)
40
+ file_key = File.basename(file_path, '.json').upcase.to_sym
41
+ json = File.read(File.expand_path(file_path, __FILE__))
42
+ cfg = JSON.parse(json).deep_symbolize_keys
43
+ config_hash[file_key] = cfg
24
44
  end
25
45
 
26
46
  # Get the list of names of the loaded authorities
data/lib/qa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Qa
2
- VERSION = "5.13.0".freeze
2
+ VERSION = "5.15.0".freeze
3
3
  end
data/lib/qa.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "qa/engine"
2
2
  require "active_record"
3
3
  require "activerecord-import"
4
+ require "qa/authority_wrapper"
4
5
 
5
6
  module Qa
6
7
  extend ActiveSupport::Autoload
@@ -30,6 +31,13 @@ module Qa
30
31
  warn "[DEPRECATED] #{in_msg}#{msg} It will be removed in the next major release."
31
32
  end
32
33
 
34
+ # Raised when the authority is not valid
35
+ class InvalidAuthorityError < RuntimeError
36
+ def initialize(authority_class)
37
+ super "Unable to initialize authority #{authority_class}"
38
+ end
39
+ end
40
+
33
41
  # Raised when the configuration directory for local authorities doesn't exist
34
42
  class ConfigDirectoryNotFound < StandardError; end
35
43
 
@@ -67,4 +75,46 @@ module Qa
67
75
 
68
76
  # Raised when data is returned but cannot be normalized
69
77
  class DataNormalizationError < StandardError; end
78
+
79
+ # @api public
80
+ # @since 5.11.0
81
+ #
82
+ # @param vocab [String]
83
+ # @param subauthority [String]
84
+ # @param context [#params, #search_header, #fetch_header]
85
+ # @param try_linked_data_config [Boolean] when true attempt to check for a linked data authority;
86
+ # this is included as an option to help preserve error messaging from the 5.10.0 branch.
87
+ # Unless you want to mirror the error messages of `Qa::TermsController#init_authority` then
88
+ # use the default value.
89
+ #
90
+ # @note :try_linked_data_config is included to preserve error message text; something which is
91
+ # extensively tested in this gem.
92
+ #
93
+ # @return [#search, #find] an authority that will respond to #search and #find; and in some cases
94
+ # #fetch. This is provided as a means of normalizing how we initialize an authority.
95
+ # And to provide a means to request an authority both within a controller request cycle as
96
+ # well as outside of that cycle.
97
+ def self.authority_for(vocab:, context:, subauthority: nil, try_linked_data_config: true)
98
+ authority = build_authority_for(vocab: vocab,
99
+ subauthority: subauthority,
100
+ try_linked_data_config: try_linked_data_config)
101
+ AuthorityWrapper.new(authority: authority, subauthority: subauthority, context: context)
102
+ end
103
+
104
+ # @api private
105
+ def self.build_authority_for(vocab:, subauthority: nil, try_linked_data_config: true)
106
+ authority_constant_name = "Qa::Authorities::#{vocab.to_s.camelcase}"
107
+ authority_constant = authority_constant_name.safe_constantize
108
+ if authority_constant.nil?
109
+ return Qa::Authorities::LinkedData::GenericAuthority.new(vocab.upcase.to_sym) if try_linked_data_config
110
+
111
+ raise InvalidAuthorityError, authority_constant_name
112
+ end
113
+
114
+ return authority_constant.new if authority_constant.is_a?(Class)
115
+ return authority_constant.subauthority_for(subauthority) if subauthority.present?
116
+
117
+ raise Qa::MissingSubAuthority, "No sub-authority provided"
118
+ end
119
+ private_class_method :build_authority_for
70
120
  end
@@ -199,7 +199,7 @@ describe Qa::TermsController, type: :controller do
199
199
 
200
200
  context "assign_fast" do
201
201
  before do
202
- stub_request(:get, "http://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc")
202
+ stub_request(:get, "https://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc")
203
203
  .with(headers: { 'Accept' => 'application/json' })
204
204
  .to_return(body: webmock_fixture("assign-fast-topical-result.json"), status: 200, headers: {})
205
205
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Qa::Authorities::AssignFast do
4
4
  let(:query) { "word (ling" }
5
- let(:expected_url) { "http://fast.oclc.org/searchfast/fastsuggest?&query=word%20ling&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
5
+ let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?&query=word%20ling&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
6
6
 
7
7
  # subauthority infrastructure
8
8
  describe "#new" do
@@ -60,7 +60,7 @@ describe Qa::Authorities::AssignFast do
60
60
 
61
61
  context "when query is blank" do
62
62
  let(:query) { "" }
63
- let(:expected_url) { "http://fast.oclc.org/searchfast/fastsuggest?&query=&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
63
+ let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?&query=&queryIndex=suggestall&queryReturn=suggestall%2Cidroot%2Cauth%2Ctype&suggest=autoSubject&rows=20&sort=usage+desc" }
64
64
 
65
65
  # server returns results but no results header
66
66
  let :results do
@@ -104,7 +104,7 @@ describe Qa::Authorities::AssignFast do
104
104
 
105
105
  context "with topical results" do
106
106
  let(:query) { "word" }
107
- let(:expected_url) { "http://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc" }
107
+ let(:expected_url) { "https://fast.oclc.org/searchfast/fastsuggest?query=word&queryIndex=suggest50&queryReturn=suggest50,idroot,auth,type&rows=20&suggest=autoSubject&sort=usage+desc" }
108
108
 
109
109
  let :results do
110
110
  stub_request(:get, expected_url)
@@ -42,6 +42,18 @@ describe Qa::Authorities::Loc do
42
42
  end
43
43
  end
44
44
 
45
+ describe ".root_fetch_slug_for" do
46
+ it "raises an error for an invalid subauthority" do
47
+ expect do
48
+ described_class.root_fetch_slug_for("no-one-would-ever-have-this-one")
49
+ end.to raise_error Qa::InvalidSubAuthority
50
+ end
51
+
52
+ it "returns the corresponding type for the given subauthority" do
53
+ expect(described_class.root_fetch_slug_for("graphicMaterials")).to eq("vocabulary")
54
+ end
55
+ end
56
+
45
57
  describe "#response" do
46
58
  subject { authority.response(url) }
47
59
  let :authority do
@@ -21,4 +21,9 @@ group :development do
21
21
  # See https://stackoverflow.com/questions/70500220/rails-7-ruby-3-1-loaderror-cannot-load-such-file-net-smtp
22
22
  gem 'net-smtp', require: false
23
23
  end
24
+
25
+ if Gem::Version.new(ENV['RAILS_VERSION']) < Gem::Version.new('7.1')
26
+ gem 'concurrent-ruby', '1.3.4'
27
+ end
28
+
24
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qa
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.13.0
4
+ version: 5.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Anderson
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2024-08-20 00:00:00.000000000 Z
19
+ date: 2025-06-02 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: activerecord-import
@@ -117,7 +117,7 @@ dependencies:
117
117
  version: '5.0'
118
118
  - - "<"
119
119
  - !ruby/object:Gem::Version
120
- version: '8.0'
120
+ version: '8.1'
121
121
  type: :runtime
122
122
  prerelease: false
123
123
  version_requirements: !ruby/object:Gem::Requirement
@@ -127,7 +127,7 @@ dependencies:
127
127
  version: '5.0'
128
128
  - - "<"
129
129
  - !ruby/object:Gem::Version
130
- version: '8.0'
130
+ version: '8.1'
131
131
  - !ruby/object:Gem::Dependency
132
132
  name: rdf
133
133
  requirement: !ruby/object:Gem::Requirement
@@ -413,6 +413,7 @@ files:
413
413
  - app/services/qa/pagination_service.rb
414
414
  - app/views/layouts/qa/application.html.erb
415
415
  - config/authorities.yml
416
+ - config/authorities/assign_fast/oclc_assign_fast.json
416
417
  - config/authorities/linked_data/loc.json
417
418
  - config/authorities/linked_data/oclc_fast.json
418
419
  - config/authorities/states.yml
@@ -501,6 +502,8 @@ files:
501
502
  - lib/qa/authorities/oclcts/generic_oclc_authority.rb
502
503
  - lib/qa/authorities/tgnlang.rb
503
504
  - lib/qa/authorities/web_service_base.rb
505
+ - lib/qa/authority_request_context.rb
506
+ - lib/qa/authority_wrapper.rb
504
507
  - lib/qa/configuration.rb
505
508
  - lib/qa/data/TGN_LANGUAGES.xml
506
509
  - lib/qa/engine.rb