qa 4.1.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
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