qa 4.1.1 → 4.2.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
  SHA1:
3
- metadata.gz: 4aa5880f67c4587fb44e2740ce97df0cecc2a119
4
- data.tar.gz: 50e47d7b72d95bf8a3a93ecc8ab8dcd5f1c442cf
3
+ metadata.gz: eb0e94ad762c71e5dd0bf2a7b99e03ee71b348e8
4
+ data.tar.gz: c8d21f4c0379b3035439e39a6b085faa314bf602
5
5
  SHA512:
6
- metadata.gz: 78b5ce231dc3fdcc74e254c5e578f5024a3ea1abb0c3696655dd4b8619a683e06d1caaa65ba2a87f683b594ecc6b8db79444ac63b6bd441d4a7821e82dc7236a
7
- data.tar.gz: 3b02f86177cc7c5f84d232619ff0b1e024cfc1c36eb9847deca5a760632ce2059cc248d15b20539b51be1cf6d98ba7e589879cd6a60873096123ea1cccffd5e6
6
+ metadata.gz: c1b33787d92489239f1530cc05b4ea3e912e7ae913ed0197a0fb1e58e13e3bf75a701e3ec5d93a15c49288a3cca0f06647ec11380bb48d3eefb0917d3f594390
7
+ data.tar.gz: 2a6613d06be5b77c2709bc95d0dfe3d78814c3271e3399578726fe1e9bb2ecd533ae9533cf92e30c02345ff09624c705a8f3895f08986f104dd2baf0124e56b1
@@ -80,6 +80,10 @@ class Qa::LinkedDataTermsController < ::ApplicationController
80
80
  "was not identified as a valid RDF format. You may need to include the linkeddata gem."
81
81
  logger.warn msg
82
82
  render json: { errors: msg }, status: :internal_server_error
83
+ rescue Qa::DataNormalizationError => e
84
+ msg = "Data Normalization Error - #{e.message}"
85
+ logger.warn msg
86
+ render json: { errors: msg }, status: :internal_server_error
83
87
  end
84
88
 
85
89
  # Return all the information for a given term given a URI
@@ -37,6 +37,7 @@ module Qa
37
37
  @ldpath = Qa::LinkedData::Config::Helper.fetch_required(property_map, :ldpath, false)
38
38
  @selectable = Qa::LinkedData::Config::Helper.fetch_boolean(property_map, :selectable, false)
39
39
  @drillable = Qa::LinkedData::Config::Helper.fetch_boolean(property_map, :drillable, false)
40
+ @optional = Qa::LinkedData::Config::Helper.fetch_boolean(property_map, :optional, Qa.config.property_map_default_for_optional)
40
41
  @expansion_label_ldpath = Qa::LinkedData::Config::Helper.fetch(property_map, :expansion_label_ldpath, nil)
41
42
  @expansion_id_ldpath = Qa::LinkedData::Config::Helper.fetch(property_map, :expansion_id_ldpath, nil)
42
43
  @prefixes = prefixes
@@ -54,6 +55,12 @@ module Qa
54
55
  @drillable
55
56
  end
56
57
 
58
+ # Should this property always be included in the extended context or is it optional (i.e. only shown if it has a value)
59
+ # @return [Boolean] true if this property is optional and will only be included in extended context if it has a value; otherwise, false
60
+ def optional?
61
+ @optional
62
+ end
63
+
57
64
  def group?
58
65
  group_id.present?
59
66
  end
@@ -6,6 +6,9 @@ module Qa
6
6
  class LdpathService
7
7
  VALUE_ON_ERROR = [].freeze
8
8
 
9
+ class_attribute :predefined_prefixes
10
+ self.predefined_prefixes = Ldpath::Transform.default_prefixes.with_indifferent_access
11
+
9
12
  # Create the ldpath program for a given ldpath.
10
13
  # @param ldpath [String] ldpath to follow to get a value from a graph (documation: http://marmotta.apache.org/ldpath/language.html)
11
14
  # @param prefixes [Hash] shortcut names for URI prefixes with key = part of predicate that is the same for all terms (e.g. { "madsrdf": "http://www.loc.gov/mads/rdf/v1#" })
@@ -37,7 +37,11 @@ module Qa
37
37
  values = Qa::LinkedData::Config::ContextPropertyMap::VALUE_ON_ERROR
38
38
  error = e.message
39
39
  end
40
+ return {} if values.blank? && property_map.optional?
41
+ property_info(values, error, context_map, property_map)
42
+ end
40
43
 
44
+ def property_info(values, error, context_map, property_map)
41
45
  property_info = {}
42
46
  property_info["group"] = context_map.group_label(property_map.group_id) if property_map.group?
43
47
  property_info["property"] = property_map.label
@@ -1,30 +1,28 @@
1
1
  {
2
2
  "QA_CONFIG_VERSION": "2.1",
3
3
  "prefixes": {
4
- "loc": "http://id.loc.gov/vocabulary/identifiers/",
5
- "skos": "http://www.w3.org/2004/02/skos/core#",
6
- "madsrdf": "http://www.loc.gov/mads/rdf/v1#",
7
- "owl": "http://www.w3.org/2002/07/owl#"
4
+ "loc": "http://id.loc.gov/vocabulary/identifiers/",
5
+ "madsrdf": "http://www.loc.gov/mads/rdf/v1#"
8
6
  },
9
7
  "term": {
10
8
  "url": {
11
9
  "@context": "http://www.w3.org/ns/hydra/context.jsonld",
12
- "@type": "IriTemplate",
10
+ "@type": "IriTemplate",
13
11
  "template": "http://id.loc.gov/authorities/{subauth}/{term_id}",
14
12
  "variableRepresentation": "BasicRepresentation",
15
13
  "mapping": [
16
14
  {
17
- "@type": "IriTemplateMapping",
15
+ "@type": "IriTemplateMapping",
18
16
  "variable": "term_id",
19
17
  "property": "hydra:freetextQuery",
20
18
  "required": true
21
19
  },
22
20
  {
23
- "@type": "IriTemplateMapping",
21
+ "@type": "IriTemplateMapping",
24
22
  "variable": "subauth",
25
23
  "property": "hydra:freetextQuery",
26
24
  "required": false,
27
- "default": "names"
25
+ "default": "names"
28
26
  }
29
27
  ]
30
28
  },
@@ -35,20 +33,21 @@
35
33
  "term_id": "ID",
36
34
  "language": ["en"],
37
35
  "results": {
38
- "id_ldpath": "loc:lccn",
39
- "label_ldpath": "skos:prefLabel :: xsd:string",
36
+ "id_ldpath": "loc:lccn | madsrdf:code",
37
+ "label_ldpath": "skos:prefLabel :: xsd:string",
40
38
  "altlabel_ldpath": "skos:altLabel :: xsd:string",
41
- "sameas_ldpath": "skos:exactMatch | owl:sameAs :: xsd:anyURI",
39
+ "sameas_ldpath": "skos:exactMatch | owl:sameAs :: xsd:anyURI",
42
40
  "narrower_ldpath": "madsrdf:hasNarrowerAuthority :: xsd:anyURI",
43
- "broader_ldpath": "madsrdf:hasBroaderAuthority :: xsd:anyURI"
41
+ "broader_ldpath": "madsrdf:hasBroaderAuthority :: xsd:anyURI"
44
42
  },
45
43
  "subauthorities": {
46
- "subjects": "subjects",
47
- "names": "names",
48
- "classification": "classification",
49
- "child_subject": "childrensSubjects",
50
- "genre": "genreForms",
51
- "demographic": "demographicTerms"
44
+ "subjects": "subjects",
45
+ "names": "names",
46
+ "classification": "classification",
47
+ "child_subject": "childrensSubjects",
48
+ "genre": "genreForms",
49
+ "demographic": "demographicTerms",
50
+ "music_performance": "performanceMediums"
52
51
  }
53
52
  },
54
53
  "search": {}
@@ -18,4 +18,9 @@ Qa.config do |config|
18
18
  # When true, prevents ldpath requests from making additional network calls. All values will come from the context graph
19
19
  # passed to the ldpath request.
20
20
  # config.limit_ldpath_to_context = true
21
+
22
+ # Define default behavior for property_map.optional? when it is not defined in the configuration for a property.
23
+ # When false, properties that do not override default optional behavior will be shown whether or not the property has a value in the graph.
24
+ # When true, properties that do not override default optional behavior will not be shown whn the property does not have a value in the graph.
25
+ # config.property_map_default_for_optional = false
21
26
  end
data/lib/qa.rb CHANGED
@@ -64,4 +64,7 @@ module Qa
64
64
  module IriTemplate
65
65
  class MissingParameter < StandardError; end
66
66
  end
67
+
68
+ # Raised when data is returned but cannot be normalized
69
+ class DataNormalizationError < StandardError; end
67
70
  end
@@ -70,13 +70,30 @@ module Qa::Authorities
70
70
  Config.config_value(term_results, :id_ldpath)
71
71
  end
72
72
 
73
+ # Return results id_predicates
74
+ # @return [Array<String>] the configured predicate to use to extract the id from the results
75
+ def term_results_id_predicates
76
+ @pred_ids ||=
77
+ begin
78
+ pred = Config.predicate_uri(term_results, :id_predicate)
79
+ pred ? [pred] : id_predicates_from_ldpath
80
+ end
81
+ end
82
+
73
83
  # Return results id_predicate
74
84
  # @return [String] the configured predicate to use to extract the id from the results
75
- def term_results_id_predicate
76
- return @pred_id unless @pred_id.blank?
77
- @pred_id = Config.predicate_uri(term_results, :id_predicate)
78
- return @pred_id unless @pred_id.blank?
79
- @pred_id = pred_id_from_ldpath
85
+ # NOTE: Customizations using this method should be updated to use `term_results_id_predicates` which returns [Array<String>] of
86
+ # id predicates. This method remains for backward compatibility only but may cause issues if used in places expecting an Array
87
+ def term_results_id_predicate(suppress_deprecation_warning: false)
88
+ unless suppress_deprecation_warning
89
+ Qa.deprecation_warning(
90
+ in_msg: 'Qa::Authorities::LinkedData::TermConfig',
91
+ msg: "`term_results_id_predicate` is deprecated; use `term_results_id_ldpath` by updating linked data " \
92
+ "term config results in authority #{authority_name} to specify as `id_ldpath`"
93
+ )
94
+ end
95
+ id_predicates = term_results_id_predicates
96
+ id_predicates.first
80
97
  end
81
98
 
82
99
  # Return results label_ldpath
@@ -222,16 +239,30 @@ module Qa::Authorities
222
239
 
223
240
  private
224
241
 
225
- def pred_id_from_ldpath
226
- # prefix example: { skos: 'http://www.w3.org/2004/02/skos/core#' }
227
- # ldpath example: 'skos:id :: xsd:string'
242
+ # Parse ldpath into an array of predicates.
243
+ # Gets ldpath (e.g. 'loc:lccn | madsrdf:code :: xsd:string') using config accessor for results id ldpath.
244
+ # Multiple paths are delineated by | which is used to split the ldpath into an array of paths.
245
+ # @return [Array<String>] the predicate for each path in the ldpath
246
+ def id_predicates_from_ldpath
228
247
  id_ldpath = term_results_id_ldpath
229
- return nil if id_ldpath.blank?
230
- tokens = id_ldpath.split(':')
248
+ return [] if id_ldpath.blank?
249
+ id_ldpath.split('|').map(&:strip).map do |path|
250
+ predicate = parse_predicate_from_single_path(path)
251
+ predicate.present? ? RDF::URI.new(predicate) : nil
252
+ end.compact
253
+ end
254
+
255
+ # Parse a single path (e.g. 'loc:lccn' where 'loc' is the ontology prefix and 'lccn' is the property name)
256
+ # Gets prefixes (e.g. { "loc": "http://id.loc.gov/vocabulary/identifiers/", "madsrdf": "http://www.loc.gov/mads/rdf/v1#" }) from authority config
257
+ # @return [String] the predicate constructed by combining the expanded prefix with the property name
258
+ def parse_predicate_from_single_path(path)
259
+ tokens = path.split(':')
231
260
  return nil if tokens.size < 2
232
261
  prefix = tokens.first.to_sym
233
262
  prefix_path = prefixes[prefix]
234
- prefix_path + tokens.second.strip
263
+ prefix_path = Qa::LinkedData::LdpathService.predefined_prefixes[prefix] if prefix_path.blank?
264
+ raise Qa::InvalidConfiguration, "Prefix '#{prefix}' is not defined in term configuration for authority #{authority_name}" if prefix_path.blank?
265
+ "#{prefix_path}#{tokens.second.strip}"
235
266
  end
236
267
 
237
268
  def summary_without_subauthority(auth_name, language)
@@ -15,8 +15,8 @@ module Qa::Authorities
15
15
  @term_config = term_config
16
16
  end
17
17
 
18
- attr_reader :term_config, :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s
19
- private :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s
18
+ attr_reader :term_config, :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size
19
+ private :full_graph, :filtered_graph, :language, :id, :uri, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size
20
20
 
21
21
  delegate :term_subauthority?, :prefixes, :authority_name, to: :term_config
22
22
 
@@ -60,6 +60,7 @@ module Qa::Authorities
60
60
 
61
61
  access_end_dt = Time.now.utc
62
62
  @access_time_s = access_end_dt - access_start_dt
63
+ @fetched_size = full_graph.triples.to_s.size if performance_data?
63
64
  Rails.logger.info("Time to receive data from authority: #{access_time_s}s")
64
65
  end
65
66
 
@@ -70,8 +71,9 @@ module Qa::Authorities
70
71
  json = perform_normalization
71
72
 
72
73
  @normalize_time_s = normalize_end_dt - normalize_start_dt
74
+ @normalized_size = json.to_s.size if performance_data?
73
75
  Rails.logger.info("Time to convert data to json: #{normalize_time_s}s")
74
- json = append_performance_data(json) if performance_data? && !jsonld?
76
+ json = append_performance_data(json) if performance_data?
75
77
  json
76
78
  end
77
79
 
@@ -108,13 +110,15 @@ module Qa::Authorities
108
110
  ldpath_map: ldpaths_for_term, predicate_map: preds_for_term)
109
111
  end
110
112
 
111
- # special processing for loc ids for backward compatibility
113
+ # Special processing for loc ids for backward compatibility. IDs may be in the form 'n123' or 'n 123'. URIs do not
114
+ # have a blank. This removes the <blank> from the ID.
112
115
  def normalize_id
113
116
  return id if expects_uri?
114
117
  loc? ? id.delete(' ') : id
115
118
  end
116
119
 
117
- # special processing for loc ids for backward compatibility
120
+ # Special processing for loc ids for backward compatibility. IDs may be in the form 'n123' or 'n 123'. This adds
121
+ # the <blank> into the ID to allow it to be found as the object of a triple in the graph.
118
122
  def loc_id
119
123
  loc_id = URI.unescape(id)
120
124
  digit_idx = loc_id.index(/\d/)
@@ -124,7 +128,7 @@ module Qa::Authorities
124
128
 
125
129
  # determine if the current authority is LOC which may require special processing of its ids for backward compatibility
126
130
  def loc?
127
- authority_name.to_s.casecmp('loc').zero?
131
+ term_config.url_config.template.starts_with? 'http://id.loc.gov/authorities/'
128
132
  end
129
133
 
130
134
  def expects_uri?
@@ -133,16 +137,31 @@ module Qa::Authorities
133
137
 
134
138
  def extract_uri
135
139
  return @uri = RDF::URI.new(id) if expects_uri?
136
- @uri = graph_service.subjects_for_object_value(graph: @filtered_graph, predicate: RDF::URI.new(term_config.term_results_id_predicate), object_value: URI.unescape(id)).first
137
- return @uri unless loc? && @uri.blank?
138
- # for backward compatibility, if an loc id as passed in fails to extract the URI, try to adding a blank to the id
139
- @uri = graph_service.subjects_for_object_value(graph: @filtered_graph, predicate: RDF::URI.new(term_config.term_results_id_predicate), object_value: loc_id).first
140
- if @uri.present?
141
- Qa.deprecation_warning(
142
- in_msg: 'Qa::Authorities::LinkedData::FindTerm',
143
- msg: 'Special processing of LOC ids is deprecated; id should be an exact match of the id in the graph'
144
- )
140
+ term_config.term_results_id_predicates.each do |id_predicate|
141
+ extract_uri_by_id(id_predicate)
142
+ break if @uri.present?
145
143
  end
144
+ raise Qa::DataNormalizationError, "Unable to extract URI based on ID: #{id}" if @uri.blank?
145
+ @uri
146
+ end
147
+
148
+ def extract_uri_by_id(id_predicate)
149
+ @uri = graph_service.subjects_for_object_value(graph: @filtered_graph,
150
+ predicate: id_predicate,
151
+ object_value: URI.unescape(id)).first
152
+ return if @uri.present? || !loc?
153
+
154
+ # NOTE: Second call to try and extract using the loc_id allows for special processing on the id for LOC authorities.
155
+ # LOC URIs do not include a blank (e.g. ends with 'n123'), but the ID in the data might (e.g. 'n 123'). If
156
+ # the ID is provided without the <blank>, this tries a second time to find it with the <blank>.
157
+ @uri = graph_service.subjects_for_object_value(graph: @filtered_graph,
158
+ predicate: id_predicate,
159
+ object_value: URI.unescape(loc_id)).first
160
+ return if @uri.blank? # only show the depercation warning if the loc_id was used
161
+ Qa.deprecation_warning(
162
+ in_msg: 'Qa::Authorities::LinkedData::FindTerm',
163
+ msg: 'Special processing of LOC ids is deprecated; id should be an exact match of the id in the graph'
164
+ )
146
165
  @uri
147
166
  end
148
167
 
@@ -168,7 +187,7 @@ module Qa::Authorities
168
187
  end
169
188
 
170
189
  def performance_data?
171
- @performance_data == true
190
+ @performance_data == true && !jsonld?
172
191
  end
173
192
 
174
193
  def preds_for_term
@@ -181,7 +200,7 @@ module Qa::Authorities
181
200
  def optional_preds
182
201
  opt_preds = {}
183
202
  opt_preds[:altlabel] = term_config.term_results_altlabel_predicate
184
- opt_preds[:id] = term_config.term_results_id_predicate
203
+ opt_preds[:id] = term_config.term_results_id_predicates
185
204
  opt_preds[:narrower] = term_config.term_results_narrower_predicate
186
205
  opt_preds[:broader] = term_config.term_results_broader_predicate
187
206
  opt_preds[:sameas] = term_config.term_results_sameas_predicate
@@ -240,9 +259,14 @@ module Qa::Authorities
240
259
  end
241
260
 
242
261
  def append_performance_data(results)
243
- performance = { predicate_count: results['predicates'].size,
262
+ pred_count = results['predicates'].present? ? results['predicates'].size : 0
263
+ performance = { predicate_count: pred_count,
244
264
  fetch_time_s: access_time_s,
245
265
  normalization_time_s: normalize_time_s,
266
+ fetched_bytes: fetched_size,
267
+ normalized_bytes: normalized_size,
268
+ fetch_bytes_per_s: fetched_size / access_time_s,
269
+ normalization_bytes_per_s: normalized_size / normalize_time_s,
246
270
  total_time_s: (access_time_s + normalize_time_s) }
247
271
  { performance: performance, results: results }
248
272
  end
@@ -15,8 +15,8 @@ module Qa::Authorities
15
15
  @search_config = search_config
16
16
  end
17
17
 
18
- attr_reader :search_config, :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s
19
- private :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s
18
+ attr_reader :search_config, :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size
19
+ private :full_graph, :filtered_graph, :language, :access_time_s, :normalize_time_s, :fetched_size, :normalized_size
20
20
 
21
21
  delegate :subauthority?, :supports_sort?, :prefixes, :authority_name, to: :search_config
22
22
 
@@ -52,6 +52,7 @@ module Qa::Authorities
52
52
 
53
53
  access_end_dt = Time.now.utc
54
54
  @access_time_s = access_end_dt - access_start_dt
55
+ @fetched_size = full_graph.triples.to_s.size if performance_data?
55
56
  Rails.logger.info("Time to receive data from authority: #{access_time_s}s")
56
57
  end
57
58
 
@@ -64,6 +65,7 @@ module Qa::Authorities
64
65
 
65
66
  normalize_end_dt = Time.now.utc
66
67
  @normalize_time_s = normalize_end_dt - normalize_start_dt
68
+ @normalized_size = json.to_s.size if performance_data?
67
69
  Rails.logger.info("Time to convert data to json: #{normalize_time_s}s")
68
70
  json = append_performance_data(json) if performance_data?
69
71
  json
@@ -171,6 +173,10 @@ module Qa::Authorities
171
173
  performance = { result_count: results.size,
172
174
  fetch_time_s: access_time_s,
173
175
  normalization_time_s: normalize_time_s,
176
+ fetched_bytes: fetched_size,
177
+ normalized_bytes: normalized_size,
178
+ fetch_bytes_per_s: fetched_size / access_time_s,
179
+ normalization_bytes_per_s: normalized_size / normalize_time_s,
174
180
  total_time_s: (access_time_s + normalize_time_s) }
175
181
  { performance: performance, results: results }
176
182
  end
@@ -42,8 +42,17 @@ module Qa
42
42
  # passed to the ldpath request.
43
43
  attr_writer :limit_ldpath_to_context
44
44
  def limit_ldpath_to_context?
45
- return true if @limit_ldpath_to_context.nil?
45
+ @limit_ldpath_to_context = true if @limit_ldpath_to_context.nil?
46
46
  @limit_ldpath_to_context
47
47
  end
48
+
49
+ # Define default behavior for property_map.optional? when it is not defined in the configuration for a property.
50
+ # When false, properties that do not override default optional behavior will be shown whether or not the property has a value in the graph.
51
+ # When true, properties that do not override default optional behavior will not be shown whn the property does not have a value in the graph.
52
+ attr_writer :property_map_default_for_optional
53
+ def property_map_default_for_optional
54
+ @property_map_default_for_optional = false if @property_map_default_for_optional.nil?
55
+ @property_map_default_for_optional
56
+ end
48
57
  end
49
58
  end
@@ -1,3 +1,3 @@
1
1
  module Qa
2
- VERSION = "4.1.1".freeze
2
+ VERSION = "4.2.0".freeze
3
3
  end
@@ -307,7 +307,9 @@ describe Qa::LinkedDataTermsController, type: :controller do
307
307
  results = JSON.parse(response.body)
308
308
  expect(results).to be_kind_of Hash
309
309
  expect(results.keys).to match_array ['performance', 'results']
310
- expect(results['performance'].keys).to match_array ['result_count', 'fetch_time_s', 'normalization_time_s', 'total_time_s']
310
+ expect(results['performance'].keys).to match_array ['result_count', 'fetch_time_s', 'normalization_time_s',
311
+ 'fetched_bytes', 'normalized_bytes', 'fetch_bytes_per_s',
312
+ 'normalization_bytes_per_s', 'total_time_s']
311
313
  expect(results['performance']['total_time_s']).to eq results['performance']['fetch_time_s'] + results['performance']['normalization_time_s']
312
314
  expect(results['performance']['result_count']).to eq 3
313
315
  expect(results['results'].count).to eq 3
@@ -336,6 +338,18 @@ describe Qa::LinkedDataTermsController, type: :controller do
336
338
  end
337
339
  end
338
340
 
341
+ context 'when data normalization error' do
342
+ before do
343
+ stub_request(:get, 'http://id.worldcat.org/fast/530369')
344
+ .to_return(status: 200, body: webmock_fixture('lod_oclc_term_bad_id.nt'), headers: { 'Content-Type' => 'application/ntriples' })
345
+ end
346
+ it 'returns 500' do
347
+ expect(Rails.logger).to receive(:warn).with("Data Normalization Error - Unable to extract URI based on ID: 530369")
348
+ get :show, params: { id: '530369', vocab: 'OCLC_FAST' }
349
+ expect(response.code).to eq('500')
350
+ end
351
+ end
352
+
339
353
  context 'when rdf format error' do
340
354
  before do
341
355
  stub_request(:get, 'http://id.worldcat.org/fast/530369').to_return(status: 200)
@@ -463,7 +477,9 @@ describe Qa::LinkedDataTermsController, type: :controller do
463
477
  results = JSON.parse(response.body)
464
478
  expect(results).to be_kind_of Hash
465
479
  expect(results.keys).to match_array ['performance', 'results']
466
- expect(results['performance'].keys).to match_array ['predicate_count', 'fetch_time_s', 'normalization_time_s', 'total_time_s']
480
+ expect(results['performance'].keys).to match_array ['predicate_count', 'fetch_time_s', 'normalization_time_s',
481
+ 'fetched_bytes', 'normalized_bytes', 'fetch_bytes_per_s',
482
+ 'normalization_bytes_per_s', 'total_time_s']
467
483
  expect(results['performance']['total_time_s']).to eq results['performance']['fetch_time_s'] + results['performance']['normalization_time_s']
468
484
  expect(results['performance']['predicate_count']).to eq 15
469
485
  expect(results['results']['predicates'].count).to eq 15
@@ -619,7 +635,9 @@ describe Qa::LinkedDataTermsController, type: :controller do
619
635
  results = JSON.parse(response.body)
620
636
  expect(results).to be_kind_of Hash
621
637
  expect(results.keys).to match_array ['performance', 'results']
622
- expect(results['performance'].keys).to match_array ['predicate_count', 'fetch_time_s', 'normalization_time_s', 'total_time_s']
638
+ expect(results['performance'].keys).to match_array ['predicate_count', 'fetch_time_s', 'normalization_time_s',
639
+ 'fetched_bytes', 'normalized_bytes', 'fetch_bytes_per_s',
640
+ 'normalization_bytes_per_s', 'total_time_s']
623
641
  expect(results['performance']['total_time_s']).to eq results['performance']['fetch_time_s'] + results['performance']['normalization_time_s']
624
642
  expect(results['performance']['predicate_count']).to eq 7
625
643
  expect(results['results']['predicates'].count).to eq 7
@@ -0,0 +1,68 @@
1
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
2
+ <madsrdf:Topic rdf:about="http://id.loc.gov/authorities/subjects/sh1234" xmlns:madsrdf="http://www.loc.gov/mads/rdf/v1#">
3
+ <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#Authority"/>
4
+ <madsrdf:authoritativeLabel xml:lang="en">More Science</madsrdf:authoritativeLabel>
5
+ <madsrdf:elementList rdf:parseType="Collection">
6
+ <madsrdf:TopicElement>
7
+ <madsrdf:elementValue xml:lang="en">More Science</madsrdf:elementValue>
8
+ </madsrdf:TopicElement>
9
+ </madsrdf:elementList>
10
+ <madsrdf:hasVariant>
11
+ <madsrdf:Topic>
12
+ <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#Variant"/>
13
+ <madsrdf:variantLabel xml:lang="en">More Natural science</madsrdf:variantLabel>
14
+ <madsrdf:elementList rdf:parseType="Collection">
15
+ <madsrdf:TopicElement>
16
+ <madsrdf:elementValue xml:lang="en">More Natural science</madsrdf:elementValue>
17
+ </madsrdf:TopicElement>
18
+ </madsrdf:elementList>
19
+ </madsrdf:Topic>
20
+ </madsrdf:hasVariant>
21
+ <madsrdf:hasVariant>
22
+ <madsrdf:Topic>
23
+ <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#Variant"/>
24
+ <madsrdf:variantLabel xml:lang="en">More Science of science</madsrdf:variantLabel>
25
+ <madsrdf:elementList rdf:parseType="Collection">
26
+ <madsrdf:TopicElement>
27
+ <madsrdf:elementValue xml:lang="en">More Science of science</madsrdf:elementValue>
28
+ </madsrdf:TopicElement>
29
+ </madsrdf:elementList>
30
+ </madsrdf:Topic>
31
+ </madsrdf:hasVariant>
32
+ <madsrdf:hasVariant>
33
+ <madsrdf:Topic>
34
+ <rdf:type rdf:resource="http://www.loc.gov/mads/rdf/v1#Variant"/>
35
+ <madsrdf:variantLabel xml:lang="en">More Sciences</madsrdf:variantLabel>
36
+ <madsrdf:elementList rdf:parseType="Collection">
37
+ <madsrdf:TopicElement>
38
+ <madsrdf:elementValue xml:lang="en">More Sciences</madsrdf:elementValue>
39
+ </madsrdf:TopicElement>
40
+ </madsrdf:elementList>
41
+ </madsrdf:Topic>
42
+ </madsrdf:hasVariant>
43
+ <identifiers:lccn xmlns:identifiers="http://id.loc.gov/vocabulary/identifiers/">BAD ID sh 1234</identifiers:lccn>
44
+ <rdf:type rdf:resource="http://www.w3.org/2004/02/skos/core#Concept"/>
45
+ <skos:prefLabel xml:lang="en" xmlns:skos="http://www.w3.org/2004/02/skos/core#">More Science</skos:prefLabel>
46
+ <skosxl:altLabel xmlns:skosxl="http://www.w3.org/2008/05/skos-xl#">
47
+ <rdf:Description>
48
+ <rdf:type rdf:resource="http://www.w3.org/2008/05/skos-xl#Label"/>
49
+ <skosxl:literalForm xml:lang="en">More Natural science</skosxl:literalForm>
50
+ </rdf:Description>
51
+ </skosxl:altLabel>
52
+ <skosxl:altLabel xmlns:skosxl="http://www.w3.org/2008/05/skos-xl#">
53
+ <rdf:Description>
54
+ <rdf:type rdf:resource="http://www.w3.org/2008/05/skos-xl#Label"/>
55
+ <skosxl:literalForm xml:lang="en">More Science of science</skosxl:literalForm>
56
+ </rdf:Description>
57
+ </skosxl:altLabel>
58
+ <skosxl:altLabel xmlns:skosxl="http://www.w3.org/2008/05/skos-xl#">
59
+ <rdf:Description>
60
+ <rdf:type rdf:resource="http://www.w3.org/2008/05/skos-xl#Label"/>
61
+ <skosxl:literalForm xml:lang="en">More Sciences</skosxl:literalForm>
62
+ </rdf:Description>
63
+ </skosxl:altLabel>
64
+ <skos:altLabel xml:lang="en" xmlns:skos="http://www.w3.org/2004/02/skos/core#">More Natural science</skos:altLabel>
65
+ <skos:altLabel xml:lang="en" xmlns:skos="http://www.w3.org/2004/02/skos/core#">More Science of science</skos:altLabel>
66
+ <skos:altLabel xml:lang="en" xmlns:skos="http://www.w3.org/2004/02/skos/core#">More Sciences</skos:altLabel>
67
+ </madsrdf:Topic>
68
+ </rdf:RDF>
@@ -0,0 +1,4 @@
1
+ <http://id.worldcat.org/fast/530369> <http://purl.org/dc/terms/identifier> "BAD_ID 530369" .
2
+ <http://id.worldcat.org/fast/530369> <http://www.w3.org/2004/02/skos/core#prefLabel> "Cornell University" .
3
+ <http://id.worldcat.org/fast/530369> <http://www.w3.org/2004/02/skos/core#altLabel> "Ithaca (N.Y.). Cornell University" .
4
+ <http://id.worldcat.org/fast/530369> <http://www.w3.org/2004/02/skos/core#sameAs> <http://id.loc.gov/authorities/names/n79021621> .
@@ -28,7 +28,9 @@ RSpec.describe Qa::Authorities::LinkedData::FindTerm do
28
28
  end
29
29
  it 'includes performance in return hash' do
30
30
  expect(results.keys).to match_array [:performance, :results]
31
- expect(results[:performance].keys).to match_array [:predicate_count, :fetch_time_s, :normalization_time_s, :total_time_s]
31
+ expect(results[:performance].keys).to match_array [:predicate_count, :fetch_time_s, :normalization_time_s,
32
+ :fetched_bytes, :normalized_bytes, :fetch_bytes_per_s,
33
+ :normalization_bytes_per_s, :total_time_s]
32
34
  expect(results[:performance][:predicate_count]).to eq 7
33
35
  expect(results[:performance][:total_time_s]).to eq results[:performance][:fetch_time_s] + results[:performance][:normalization_time_s]
34
36
  end
@@ -90,90 +92,145 @@ RSpec.describe Qa::Authorities::LinkedData::FindTerm do
90
92
  .to include('Cornell University', 'Ithaca (N.Y.). Cornell University', "Kornel\\xCA\\xB9skii universitet",
91
93
  "K\\xCA\\xBBang-nai-erh ta hs\\xC3\\xBCeh")
92
94
  end
95
+
96
+ context "ID in graph doesn't match ID in request URI" do
97
+ before do
98
+ stub_request(:get, 'http://id.worldcat.org/fast/530369')
99
+ .to_return(status: 200, body: webmock_fixture('lod_oclc_term_bad_id.nt'), headers: { 'Content-Type' => 'application/ntriples' })
100
+ end
101
+
102
+ it 'raises DataNormalizationError' do
103
+ expect { lod_oclc.find('530369') }.to raise_error Qa::DataNormalizationError, "Unable to extract URI based on ID: 530369"
104
+ end
105
+ end
93
106
  end
94
107
  end
95
108
 
96
109
  context 'in LOC authority' do
97
110
  context 'term found' do
98
- before do
99
- stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
100
- .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
101
- stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh1234')
102
- .to_return(status: 200, body: webmock_fixture('lod_loc_second_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
103
- end
111
+ context 'when id requires special processing for <blank> in id' do
112
+ before do
113
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
114
+ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
115
+ end
104
116
 
105
- let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') }
106
- let(:second_results) { lod_loc.find('sh 1234', subauth: 'subjects') }
107
- let(:results_without_blank) { lod_loc.find('sh85118553', subauth: 'subjects') }
117
+ let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') }
108
118
 
109
- it 'has correct primary predicate values' do
110
- expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
111
- expect(results[:uri]).to be_kind_of String
112
- expect(results[:id]).to eq 'sh 85118553'
113
- expect(results[:label]).to eq ['Science']
114
- expect(results[:altlabel]).to include('Natural science', 'Science of science', 'Sciences')
115
- expect(results[:narrower]).to include('http://id.loc.gov/authorities/subjects/sh92004048')
116
- expect(results[:narrower].first).to be_kind_of String
117
- end
119
+ it 'has correct primary predicate values' do
120
+ expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
121
+ expect(results[:uri]).to be_kind_of String
122
+ expect(results[:id]).to eq 'sh 85118553'
123
+ expect(results[:label]).to eq ['Science']
124
+ expect(results[:altlabel]).to include('Natural science', 'Science of science', 'Sciences')
125
+ expect(results[:narrower]).to include('http://id.loc.gov/authorities/subjects/sh92004048')
126
+ expect(results[:narrower].first).to be_kind_of String
127
+ end
118
128
 
119
- it 'has correct number of predicates in pred-obj list' do
120
- expect(results['predicates'].count).to eq 15
121
- end
129
+ it 'has correct number of predicates in pred-obj list' do
130
+ expect(results['predicates'].count).to eq 15
131
+ end
122
132
 
123
- it 'has primary predicates in pred-obj list' do
124
- expect(results['predicates']['http://id.loc.gov/vocabulary/identifiers/lccn']).to eq ['sh 85118553']
125
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']).to eq ['Science']
126
- expect(results['predicates']['http://www.w3.org/2004/02/skos/core#prefLabel']).to eq ['Science']
127
- expect(results['predicates']['http://www.w3.org/2004/02/skos/core#altLabel']).to include('Natural science', 'Science of science', 'Sciences')
133
+ it 'has primary predicates in pred-obj list' do
134
+ expect(results['predicates']['http://id.loc.gov/vocabulary/identifiers/lccn']).to eq ['sh 85118553']
135
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#authoritativeLabel']).to eq ['Science']
136
+ expect(results['predicates']['http://www.w3.org/2004/02/skos/core#prefLabel']).to eq ['Science']
137
+ expect(results['predicates']['http://www.w3.org/2004/02/skos/core#altLabel']).to include('Natural science', 'Science of science', 'Sciences')
138
+ end
139
+
140
+ it 'has loc mads predicate values' do
141
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#classification']).to eq ['Q']
142
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#isMemberOfMADSCollection'])
143
+ .to include('http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings',
144
+ 'http://id.loc.gov/authorities/subjects/collection_LCSH_General',
145
+ 'http://id.loc.gov/authorities/subjects/collection_SubdivideGeographically')
146
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#hasCloseExternalAuthority'])
147
+ .to include('http://data.bnf.fr/ark:/12148/cb12321484k', 'http://data.bnf.fr/ark:/12148/cb119673416',
148
+ 'http://data.bnf.fr/ark:/12148/cb119934236', 'http://data.bnf.fr/ark:/12148/cb12062047t',
149
+ 'http://data.bnf.fr/ark:/12148/cb119469567', 'http://data.bnf.fr/ark:/12148/cb11933232c',
150
+ 'http://data.bnf.fr/ark:/12148/cb122890536', 'http://data.bnf.fr/ark:/12148/cb121155321',
151
+ 'http://data.bnf.fr/ark:/12148/cb15556043g', 'http://data.bnf.fr/ark:/12148/cb123662513',
152
+ 'http://d-nb.info/gnd/4066562-8', 'http://data.bnf.fr/ark:/12148/cb120745812',
153
+ 'http://data.bnf.fr/ark:/12148/cb11973101n', 'http://data.bnf.fr/ark:/12148/cb13328497r')
154
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme'])
155
+ .to eq ['http://id.loc.gov/authorities/subjects']
156
+ expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#editorialNote'])
157
+ .to eq ['headings beginning with the word [Scientific;] and subdivision [Science] under ethnic groups and individual wars, e.g. [World War, 1939-1945--Science]']
158
+ end
159
+
160
+ it 'has more unspecified predicate values' do
161
+ expect(results['predicates']['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']).to include('http://www.loc.gov/mads/rdf/v1#Topic', 'http://www.loc.gov/mads/rdf/v1#Authority', 'http://www.w3.org/2004/02/skos/core#Concept')
162
+ expect(results['predicates']['http://www.w3.org/2002/07/owl#sameAs']).to include('info:lc/authorities/sh85118553', 'http://id.loc.gov/authorities/sh85118553#concept')
163
+ expect(results['predicates']['http://www.w3.org/2004/02/skos/core#closeMatch'])
164
+ .to include('http://data.bnf.fr/ark:/12148/cb12321484k', 'http://data.bnf.fr/ark:/12148/cb119673416',
165
+ 'http://data.bnf.fr/ark:/12148/cb119934236', 'http://data.bnf.fr/ark:/12148/cb12062047t',
166
+ 'http://data.bnf.fr/ark:/12148/cb119469567', 'http://data.bnf.fr/ark:/12148/cb11933232c',
167
+ 'http://data.bnf.fr/ark:/12148/cb122890536', 'http://data.bnf.fr/ark:/12148/cb121155321',
168
+ 'http://data.bnf.fr/ark:/12148/cb15556043g', 'http://data.bnf.fr/ark:/12148/cb123662513',
169
+ 'http://d-nb.info/gnd/4066562-8', 'http://data.bnf.fr/ark:/12148/cb120745812',
170
+ 'http://data.bnf.fr/ark:/12148/cb11973101n', 'http://data.bnf.fr/ark:/12148/cb13328497r')
171
+ expect(results['predicates']['http://www.w3.org/2004/02/skos/core#editorial'])
172
+ .to eq ['headings beginning with the word [Scientific;] and subdivision [Science] under ethnic groups and individual wars, e.g. [World War, 1939-1945--Science]']
173
+ expect(results['predicates']['http://www.w3.org/2004/02/skos/core#inScheme']).to eq ['http://id.loc.gov/authorities/subjects']
174
+ end
128
175
  end
129
176
 
130
- it 'has loc mads predicate values' do
131
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#classification']).to eq ['Q']
132
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#isMemberOfMADSCollection'])
133
- .to include('http://id.loc.gov/authorities/subjects/collection_LCSHAuthorizedHeadings',
134
- 'http://id.loc.gov/authorities/subjects/collection_LCSH_General',
135
- 'http://id.loc.gov/authorities/subjects/collection_SubdivideGeographically')
136
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#hasCloseExternalAuthority'])
137
- .to include('http://data.bnf.fr/ark:/12148/cb12321484k', 'http://data.bnf.fr/ark:/12148/cb119673416',
138
- 'http://data.bnf.fr/ark:/12148/cb119934236', 'http://data.bnf.fr/ark:/12148/cb12062047t',
139
- 'http://data.bnf.fr/ark:/12148/cb119469567', 'http://data.bnf.fr/ark:/12148/cb11933232c',
140
- 'http://data.bnf.fr/ark:/12148/cb122890536', 'http://data.bnf.fr/ark:/12148/cb121155321',
141
- 'http://data.bnf.fr/ark:/12148/cb15556043g', 'http://data.bnf.fr/ark:/12148/cb123662513',
142
- 'http://d-nb.info/gnd/4066562-8', 'http://data.bnf.fr/ark:/12148/cb120745812',
143
- 'http://data.bnf.fr/ark:/12148/cb11973101n', 'http://data.bnf.fr/ark:/12148/cb13328497r')
144
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#isMemberOfMADSScheme'])
145
- .to eq ['http://id.loc.gov/authorities/subjects']
146
- expect(results['predicates']['http://www.loc.gov/mads/rdf/v1#editorialNote'])
147
- .to eq ['headings beginning with the word [Scientific;] and subdivision [Science] under ethnic groups and individual wars, e.g. [World War, 1939-1945--Science]']
177
+ context 'when multiple requests are made' do
178
+ before do
179
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
180
+ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
181
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh1234')
182
+ .to_return(status: 200, body: webmock_fixture('lod_loc_second_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
183
+ end
184
+
185
+ let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') }
186
+ let(:second_results) { lod_loc.find('sh 1234', subauth: 'subjects') }
187
+
188
+ it 'has correct primary predicate values for second request' do
189
+ expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
190
+ expect(second_results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh1234'
191
+ expect(second_results[:uri]).to be_kind_of String
192
+ expect(second_results[:id]).to eq 'sh 1234'
193
+ expect(second_results[:label]).to eq ['More Science']
194
+ expect(second_results[:altlabel]).to include('More Natural science', 'More Science of science', 'More Sciences')
195
+ end
148
196
  end
149
197
 
150
- it 'has more unspecified predicate values' do
151
- expect(results['predicates']['http://www.w3.org/1999/02/22-rdf-syntax-ns#type']).to include('http://www.loc.gov/mads/rdf/v1#Topic', 'http://www.loc.gov/mads/rdf/v1#Authority', 'http://www.w3.org/2004/02/skos/core#Concept')
152
- expect(results['predicates']['http://www.w3.org/2002/07/owl#sameAs']).to include('info:lc/authorities/sh85118553', 'http://id.loc.gov/authorities/sh85118553#concept')
153
- expect(results['predicates']['http://www.w3.org/2004/02/skos/core#closeMatch'])
154
- .to include('http://data.bnf.fr/ark:/12148/cb12321484k', 'http://data.bnf.fr/ark:/12148/cb119673416',
155
- 'http://data.bnf.fr/ark:/12148/cb119934236', 'http://data.bnf.fr/ark:/12148/cb12062047t',
156
- 'http://data.bnf.fr/ark:/12148/cb119469567', 'http://data.bnf.fr/ark:/12148/cb11933232c',
157
- 'http://data.bnf.fr/ark:/12148/cb122890536', 'http://data.bnf.fr/ark:/12148/cb121155321',
158
- 'http://data.bnf.fr/ark:/12148/cb15556043g', 'http://data.bnf.fr/ark:/12148/cb123662513',
159
- 'http://d-nb.info/gnd/4066562-8', 'http://data.bnf.fr/ark:/12148/cb120745812',
160
- 'http://data.bnf.fr/ark:/12148/cb11973101n', 'http://data.bnf.fr/ark:/12148/cb13328497r')
161
- expect(results['predicates']['http://www.w3.org/2004/02/skos/core#editorial'])
162
- .to eq ['headings beginning with the word [Scientific;] and subdivision [Science] under ethnic groups and individual wars, e.g. [World War, 1939-1945--Science]']
163
- expect(results['predicates']['http://www.w3.org/2004/02/skos/core#inScheme']).to eq ['http://id.loc.gov/authorities/subjects']
198
+ context 'when id does not have a <blank>' do
199
+ before do
200
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
201
+ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
202
+ end
203
+
204
+ let(:results_without_blank) { lod_loc.find('sh85118553', subauth: 'subjects') }
205
+
206
+ it 'extracts correct uri' do
207
+ expect(results_without_blank[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
208
+ end
164
209
  end
165
210
 
166
- it 'has correct primary predicate values for second request' do
167
- expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
168
- expect(second_results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh1234'
169
- expect(second_results[:uri]).to be_kind_of String
170
- expect(second_results[:id]).to eq 'sh 1234'
171
- expect(second_results[:label]).to eq ['More Science']
172
- expect(second_results[:altlabel]).to include('More Natural science', 'More Science of science', 'More Sciences')
211
+ context "ID in graph doesn't match ID in request URI" do
212
+ before do
213
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
214
+ .to_return(status: 200, body: webmock_fixture('lod_loc_term_bad_id.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
215
+ end
216
+
217
+ it 'raises DataNormalizationError' do
218
+ expect { lod_loc.find('sh85118553', subauth: 'subjects') }.to raise_error Qa::DataNormalizationError, "Unable to extract URI based on ID: sh85118553"
219
+ end
173
220
  end
174
221
 
175
- it 'extracts correct uri when loc id does not have blank' do
176
- expect(results_without_blank[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
222
+ context 'when alternate authority name is used to access loc' do
223
+ before do
224
+ stub_request(:get, 'http://id.loc.gov/authorities/subjects/sh85118553')
225
+ .to_return(status: 200, body: webmock_fixture('lod_loc_term_found.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
226
+ allow(lod_loc.term_config).to receive(:authority_name).and_return('ALT_LOC_AUTHORITY')
227
+ end
228
+
229
+ let(:results) { lod_loc.find('sh 85118553', subauth: 'subjects') }
230
+
231
+ it 'does special processing to remove blank from id' do
232
+ expect(results[:uri]).to eq 'http://id.loc.gov/authorities/subjects/sh85118553'
233
+ end
177
234
  end
178
235
  end
179
236
  end
@@ -16,7 +16,9 @@ RSpec.describe Qa::Authorities::LinkedData::SearchQuery do
16
16
  it 'includes performance in return hash' do
17
17
  expect(results).to be_kind_of Hash
18
18
  expect(results.keys).to match_array [:performance, :results]
19
- expect(results[:performance].keys).to match_array [:result_count, :fetch_time_s, :normalization_time_s, :total_time_s]
19
+ expect(results[:performance].keys).to match_array [:result_count, :fetch_time_s, :normalization_time_s,
20
+ :fetched_bytes, :normalized_bytes, :fetch_bytes_per_s,
21
+ :normalization_bytes_per_s, :total_time_s]
20
22
  expect(results[:performance][:total_time_s]).to eq results[:performance][:fetch_time_s] + results[:performance][:normalization_time_s]
21
23
  expect(results[:performance][:result_count]).to eq 3
22
24
  expect(results[:results].count).to eq 3
@@ -6,6 +6,7 @@ describe Qa::Authorities::LinkedData::TermConfig do
6
6
  let(:min_config) { Qa::Authorities::LinkedData::Config.new(:LOD_MIN_CONFIG).term }
7
7
  let(:search_only_config) { Qa::Authorities::LinkedData::Config.new(:LOD_SEARCH_ONLY_CONFIG).term }
8
8
  let(:encoding_config) { Qa::Authorities::LinkedData::Config.new(:LOD_ENCODING_CONFIG).term }
9
+ let(:loc_config) { Qa::Authorities::LinkedData::Config.new(:LOC).term }
9
10
 
10
11
  let(:ldpath_results_config) do
11
12
  {
@@ -201,17 +202,30 @@ describe Qa::Authorities::LinkedData::TermConfig do
201
202
  it 'returns nil if only search configuration is defined' do
202
203
  expect(search_only_config.term_results).to eq nil
203
204
  end
204
- it 'returns hash of predicates' do
205
+ it 'returns hash of predicates or ldpaths' do
205
206
  expect(full_config.term_results).to eq results_config
206
207
  end
207
208
  end
208
209
 
209
- describe '#term_results_id_predicate' do
210
+ describe '#term_results_id_predicates' do
210
211
  it 'returns nil if only search configuration is defined' do
211
- expect(search_only_config.term_results_id_predicate).to eq nil
212
+ expect(search_only_config.term_results_id_predicates).to eq []
212
213
  end
213
- it 'returns the predicate that holds the ID in term results' do
214
- expect(full_config.term_results_id_predicate).to eq RDF::URI('http://purl.org/dc/terms/identifier')
214
+ it 'returns array of one predicates when only one defined' do
215
+ expect(full_config.term_results_id_predicates).to eq [RDF::URI('http://purl.org/dc/terms/identifier')]
216
+ end
217
+ it 'returns array of multiple predicates when ldpath specifies more than one path' do
218
+ expect(loc_config.term_results_id_predicates).to match_array [RDF::URI('http://id.loc.gov/vocabulary/identifiers/lccn'),
219
+ RDF::URI('http://www.loc.gov/mads/rdf/v1#code')]
220
+ end
221
+ it 'returns array of predicates when prefix is one of the ldpath gem predefined prefixes' do
222
+ allow(full_config).to receive(:prefixes).and_return({})
223
+ allow(full_config).to receive(:term_results).and_return(id_ldpath: 'dc:identifier')
224
+ expect(full_config.term_results_id_predicates).to eq [RDF::URI('http://purl.org/dc/elements/1.1/identifier')]
225
+ end
226
+ it 'raises an error if predicate prefix is not defined' do
227
+ allow(loc_config).to receive(:prefixes).and_return({})
228
+ expect { loc_config.term_results_id_predicates }.to raise_error Qa::InvalidConfiguration, "Prefix 'loc' is not defined in term configuration for authority LOC"
215
229
  end
216
230
  end
217
231
 
@@ -266,7 +280,7 @@ describe Qa::Authorities::LinkedData::TermConfig do
266
280
  it 'returns nil if only search configuration is defined' do
267
281
  expect(search_only_config.term_results_altlabel_ldpath).to eq nil
268
282
  end
269
- it 'return nil if altlabel predicate is not defined' do
283
+ it 'return nil if altlabel ldpath is not defined' do
270
284
  expect(min_config.term_results_altlabel_ldpath).to eq nil
271
285
  end
272
286
 
@@ -294,7 +308,7 @@ describe Qa::Authorities::LinkedData::TermConfig do
294
308
  it 'returns nil if only search configuration is defined' do
295
309
  expect(search_only_config.term_results_broader_ldpath).to eq nil
296
310
  end
297
- it 'return nil if broader predicate is not defined' do
311
+ it 'return nil if broader ldpath is not defined' do
298
312
  expect(min_config.term_results_broader_ldpath).to eq nil
299
313
  end
300
314
 
@@ -322,7 +336,7 @@ describe Qa::Authorities::LinkedData::TermConfig do
322
336
  it 'returns nil if only search configuration is defined' do
323
337
  expect(search_only_config.term_results_narrower_ldpath).to eq nil
324
338
  end
325
- it 'return nil if narrower predicate is not defined' do
339
+ it 'return nil if narrower ldpath is not defined' do
326
340
  expect(min_config.term_results_narrower_ldpath).to eq nil
327
341
  end
328
342
 
@@ -350,7 +364,7 @@ describe Qa::Authorities::LinkedData::TermConfig do
350
364
  it 'returns nil if only search configuration is defined' do
351
365
  expect(search_only_config.term_results_sameas_ldpath).to eq nil
352
366
  end
353
- it 'return nil if sameas predicate is not defined' do
367
+ it 'return nil if sameas ldpath is not defined' do
354
368
  expect(min_config.term_results_sameas_ldpath).to eq nil
355
369
  end
356
370
 
@@ -76,14 +76,52 @@ RSpec.describe Qa::Configuration do
76
76
  end
77
77
  end
78
78
 
79
- context 'when configured' do
79
+ context 'when configured as true' do
80
+ before do
81
+ subject.limit_ldpath_to_context = true
82
+ end
83
+
84
+ it 'returns true' do
85
+ expect(subject.limit_ldpath_to_context?).to be true
86
+ end
87
+ end
88
+
89
+ context 'when configured as false' do
80
90
  before do
81
91
  subject.limit_ldpath_to_context = false
82
92
  end
83
93
 
84
- it 'returns the configured value' do
94
+ it 'returns false' do
85
95
  expect(subject.limit_ldpath_to_context?).to be false
86
96
  end
87
97
  end
88
98
  end
99
+
100
+ describe '#property_map_default_for_optional' do
101
+ context 'when NOT configured' do
102
+ it 'returns false' do
103
+ expect(subject.property_map_default_for_optional).to be false
104
+ end
105
+ end
106
+
107
+ context 'when configured as true' do
108
+ before do
109
+ subject.property_map_default_for_optional = true
110
+ end
111
+
112
+ it 'returns the true' do
113
+ expect(subject.property_map_default_for_optional).to be true
114
+ end
115
+ end
116
+
117
+ context 'when configured as false' do
118
+ before do
119
+ subject.property_map_default_for_optional = false
120
+ end
121
+
122
+ it 'returns false' do
123
+ expect(subject.property_map_default_for_optional).to be false
124
+ end
125
+ end
126
+ end
89
127
  end
@@ -109,6 +109,43 @@ RSpec.describe Qa::LinkedData::Config::ContextPropertyMap do
109
109
  end
110
110
  end
111
111
 
112
+ describe '#optional?' do
113
+ context 'when map has optional: true' do
114
+ before { property_map[:optional] = true }
115
+
116
+ it 'returns true' do
117
+ expect(subject.optional?).to be true
118
+ end
119
+ end
120
+
121
+ context 'when map has optional: false' do
122
+ before { property_map[:optional] = false }
123
+
124
+ it 'returns false' do
125
+ expect(subject.optional?).to be false
126
+ end
127
+ end
128
+
129
+ context 'when optional: is not defined in the map' do
130
+ before { property_map.delete(:optional) }
131
+
132
+ context 'and property_map_default_for_optional is true' do
133
+ before { allow(Qa.config).to receive(:property_map_default_for_optional).and_return(true) }
134
+ it 'returns true' do
135
+ Qa.config.property_map_default_for_optional = true
136
+ expect(subject.optional?).to be true
137
+ end
138
+ end
139
+
140
+ context 'and property_map_default_for_optional is false' do
141
+ before { allow(Qa.config).to receive(:property_map_default_for_optional).and_return(false) }
142
+ it 'returns false' do
143
+ expect(subject.optional?).to be false
144
+ end
145
+ end
146
+ end
147
+ end
148
+
112
149
  describe '#label' do
113
150
  context 'when map defines property_label_i18n key' do
114
151
  context 'and i18n translation is defined in locales' do
@@ -58,4 +58,13 @@ RSpec.describe Qa::LinkedData::LdpathService do
58
58
  end
59
59
  end
60
60
  end
61
+
62
+ describe '.predefined_prefixes' do
63
+ subject { described_class.predefined_prefixes }
64
+ it 'includes prefixes defined by ldpath' do
65
+ # only checking for a few prefixes as opposed to the entire list since the gem may expand the list
66
+ expect(subject.keys).to include("rdf", "rdfs", "owl", "skos", "dc")
67
+ expect(subject[:rdf]).to be_present
68
+ end
69
+ end
61
70
  end
@@ -7,17 +7,19 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
7
7
  let(:context_map) { instance_double(Qa::LinkedData::Config::ContextMap) }
8
8
  let(:subject_uri) { instance_double(RDF::URI) }
9
9
 
10
- let(:context_properties) { [birth_date_property_map, death_date_property_map, occupation_property_map] }
10
+ let(:context_properties) { [birth_date_property_map, death_date_property_map, occupation_property_map, missing_property_map] }
11
11
 
12
12
  let(:birth_date_property_map) { instance_double(Qa::LinkedData::Config::ContextPropertyMap) }
13
13
  let(:death_date_property_map) { instance_double(Qa::LinkedData::Config::ContextPropertyMap) }
14
14
  let(:occupation_property_map) { instance_double(Qa::LinkedData::Config::ContextPropertyMap) }
15
+ let(:missing_property_map) { instance_double(Qa::LinkedData::Config::ContextPropertyMap) }
15
16
 
16
17
  let(:group_id) { 'dates' }
17
18
 
18
19
  let(:birth_date_values) { ['10/15/1943'] }
19
20
  let(:death_date_values) { ['12/17/2018'] }
20
21
  let(:occupation_values) { ['Actress', 'Director', 'Producer'] }
22
+ let(:missing_values) { [] }
21
23
 
22
24
  before do
23
25
  allow(context_map).to receive(:properties).and_return(context_properties)
@@ -29,6 +31,7 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
29
31
  allow(birth_date_property_map).to receive(:selectable?).and_return(false)
30
32
  allow(birth_date_property_map).to receive(:drillable?).and_return(false)
31
33
  allow(birth_date_property_map).to receive(:expand_uri?).and_return(false)
34
+ allow(birth_date_property_map).to receive(:optional?).and_return(false)
32
35
 
33
36
  allow(death_date_property_map).to receive(:label).and_return('Death')
34
37
  allow(death_date_property_map).to receive(:values).with(graph, subject_uri).and_return(death_date_values)
@@ -36,6 +39,7 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
36
39
  allow(death_date_property_map).to receive(:selectable?).and_return(false)
37
40
  allow(death_date_property_map).to receive(:drillable?).and_return(false)
38
41
  allow(death_date_property_map).to receive(:expand_uri?).and_return(false)
42
+ allow(death_date_property_map).to receive(:optional?).and_return(false)
39
43
 
40
44
  allow(occupation_property_map).to receive(:label).and_return('Occupation')
41
45
  allow(occupation_property_map).to receive(:values).with(graph, subject_uri).and_return(occupation_values)
@@ -43,6 +47,15 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
43
47
  allow(occupation_property_map).to receive(:selectable?).and_return(false)
44
48
  allow(occupation_property_map).to receive(:drillable?).and_return(false)
45
49
  allow(occupation_property_map).to receive(:expand_uri?).and_return(false)
50
+ allow(occupation_property_map).to receive(:optional?).and_return(false)
51
+
52
+ allow(missing_property_map).to receive(:label).and_return('Property with NO values')
53
+ allow(missing_property_map).to receive(:values).with(graph, subject_uri).and_return(missing_values)
54
+ allow(missing_property_map).to receive(:group?).and_return(false)
55
+ allow(missing_property_map).to receive(:selectable?).and_return(false)
56
+ allow(missing_property_map).to receive(:drillable?).and_return(false)
57
+ allow(missing_property_map).to receive(:expand_uri?).and_return(false)
58
+ allow(missing_property_map).to receive(:optional?).and_return(true)
46
59
  end
47
60
 
48
61
  describe '.map_context' do
@@ -115,6 +128,21 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
115
128
  end
116
129
  end
117
130
 
131
+ context 'when optional? is false' do
132
+ before { allow(missing_property_map).to receive(:optional?).and_return(false) }
133
+ it 'includes property with blank values' do
134
+ result = find_property_to_test(subject, 'Property with NO values')
135
+ expect(result['values']).to eq []
136
+ end
137
+ end
138
+
139
+ context 'when optional? is true' do
140
+ before { allow(missing_property_map).to receive(:optional?).and_return(true) }
141
+ it 'property with blank values is not added to results' do
142
+ expect { find_property_to_test(subject, 'Property with NO values') }.to raise_error StandardError, "property 'Property with NO values' not found"
143
+ end
144
+ end
145
+
118
146
  context 'when error occurs' do
119
147
  let(:cause) { I18n.t('qa.linked_data.ldpath.parse_error') }
120
148
  before { allow(occupation_property_map).to receive(:values).with(graph, subject_uri).and_raise(cause) }
@@ -132,6 +160,6 @@ RSpec.describe Qa::LinkedData::Mapper::ContextMapperService do
132
160
  next unless r['property'] == label
133
161
  return r
134
162
  end
135
- raise "property (#{label}) to test not found"
163
+ raise StandardError, "property '#{label}' not found"
136
164
  end
137
165
  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: 4.1.1
4
+ version: 4.2.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: 2019-05-08 00:00:00.000000000 Z
19
+ date: 2019-06-08 00:00:00.000000000 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: activerecord-import
@@ -488,10 +488,12 @@ files:
488
488
  - spec/fixtures/lod_lang_term_enfrde.rdf.xml
489
489
  - spec/fixtures/lod_lang_term_fr.rdf.xml
490
490
  - spec/fixtures/lod_loc_second_term_found.rdf.xml
491
+ - spec/fixtures/lod_loc_term_bad_id.rdf.xml
491
492
  - spec/fixtures/lod_loc_term_found.rdf.xml
492
493
  - spec/fixtures/lod_oclc_all_query_3_results.rdf.xml
493
494
  - spec/fixtures/lod_oclc_personalName_query_3_results.rdf.xml
494
495
  - spec/fixtures/lod_oclc_query_no_results.rdf.xml
496
+ - spec/fixtures/lod_oclc_term_bad_id.nt
495
497
  - spec/fixtures/lod_oclc_term_found.rdf.xml
496
498
  - spec/fixtures/lod_search_with_blanknode_subjects.nt
497
499
  - spec/fixtures/lod_term_with_blanknode_objects.nt
@@ -627,6 +629,7 @@ test_files:
627
629
  - spec/fixtures/oclcts-response-mesh-3.txt
628
630
  - spec/fixtures/loc-names-response.txt
629
631
  - spec/fixtures/oclcts-response-mesh-2.txt
632
+ - spec/fixtures/lod_loc_term_bad_id.rdf.xml
630
633
  - spec/fixtures/aat-response.txt
631
634
  - spec/fixtures/journals-result.json
632
635
  - spec/fixtures/discogs-search-response-no-subauth.json
@@ -696,6 +699,7 @@ test_files:
696
699
  - spec/fixtures/geonames-response.json
697
700
  - spec/fixtures/tgn-response.txt
698
701
  - spec/fixtures/lod_oclc_term_found.rdf.xml
702
+ - spec/fixtures/lod_oclc_term_bad_id.nt
699
703
  - spec/fixtures/lod_search_with_blanknode_subjects.nt
700
704
  - spec/fixtures/assign-fast-noheader.json
701
705
  - spec/fixtures/lod_loc_second_term_found.rdf.xml