qa 3.1.0 → 4.0.0.rc1

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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +16 -548
  3. data/app/controllers/qa/linked_data_terms_controller.rb +64 -42
  4. data/app/controllers/qa/terms_controller.rb +14 -6
  5. data/app/models/qa/iri_template/url_config.rb +47 -0
  6. data/app/models/qa/iri_template/variable_map.rb +62 -0
  7. data/app/models/qa/linked_data/config/context_map.rb +77 -0
  8. data/app/models/qa/linked_data/config/context_property_map.rb +144 -0
  9. data/app/models/qa/linked_data/config/helper.rb +34 -0
  10. data/app/services/qa/iri_template_service.rb +31 -0
  11. data/{lib/qa/authorities → app/services/qa}/linked_data/authority_service.rb +3 -4
  12. data/app/services/qa/linked_data/authority_url_service.rb +48 -0
  13. data/app/services/qa/linked_data/deep_sort_service.rb +238 -0
  14. data/app/services/qa/linked_data/graph_service.rb +106 -0
  15. data/app/services/qa/linked_data/language_service.rb +30 -0
  16. data/app/services/qa/linked_data/language_sort_service.rb +81 -0
  17. data/app/services/qa/linked_data/mapper/context_mapper_service.rb +59 -0
  18. data/app/services/qa/linked_data/mapper/graph_mapper_service.rb +40 -0
  19. data/app/services/qa/linked_data/mapper/search_results_mapper_service.rb +70 -0
  20. data/config/authorities/linked_data/loc.json +5 -2
  21. data/config/authorities/linked_data/oclc_fast.json +3 -2
  22. data/config/initializers/linked_data_authorities.rb +1 -1
  23. data/config/locales/qa.en.yml +9 -0
  24. data/lib/generators/qa/install/templates/config/initializers/qa.rb +4 -0
  25. data/lib/qa.rb +8 -0
  26. data/lib/qa/authorities/assign_fast/generic_authority.rb +1 -1
  27. data/lib/qa/authorities/base.rb +0 -11
  28. data/lib/qa/authorities/crossref/generic_authority.rb +1 -1
  29. data/lib/qa/authorities/geonames.rb +1 -1
  30. data/lib/qa/authorities/getty/aat.rb +7 -2
  31. data/lib/qa/authorities/getty/tgn.rb +7 -2
  32. data/lib/qa/authorities/getty/ulan.rb +7 -2
  33. data/lib/qa/authorities/linked_data.rb +0 -1
  34. data/lib/qa/authorities/linked_data/config.rb +29 -28
  35. data/lib/qa/authorities/linked_data/config/search_config.rb +21 -79
  36. data/lib/qa/authorities/linked_data/config/term_config.rb +7 -77
  37. data/lib/qa/authorities/linked_data/find_term.rb +25 -17
  38. data/lib/qa/authorities/linked_data/generic_authority.rb +6 -5
  39. data/lib/qa/authorities/linked_data/rdf_helper.rb +6 -73
  40. data/lib/qa/authorities/linked_data/search_query.rb +54 -101
  41. data/lib/qa/authorities/loc/generic_authority.rb +4 -4
  42. data/lib/qa/authorities/web_service_base.rb +1 -8
  43. data/lib/qa/configuration.rb +7 -0
  44. data/lib/qa/version.rb +1 -1
  45. data/lib/tasks/mesh.rake +19 -18
  46. data/spec/controllers/linked_data_terms_controller_spec.rb +51 -1
  47. data/spec/controllers/terms_controller_spec.rb +15 -15
  48. data/spec/fixtures/authorities/linked_data/lod_encoding_config.json +2 -1
  49. data/spec/fixtures/authorities/linked_data/lod_full_config.json +56 -2
  50. data/spec/fixtures/authorities/linked_data/lod_full_config_1_0.json +164 -0
  51. data/spec/fixtures/authorities/linked_data/lod_lang_defaults.json +5 -4
  52. data/spec/fixtures/authorities/linked_data/lod_lang_multi_defaults.json +3 -2
  53. data/spec/fixtures/authorities/linked_data/lod_lang_no_defaults.json +3 -2
  54. data/spec/fixtures/authorities/linked_data/lod_lang_param.json +3 -2
  55. data/spec/fixtures/authorities/linked_data/lod_min_config.json +3 -2
  56. data/spec/fixtures/authorities/linked_data/lod_search_only_config.json +2 -1
  57. data/spec/fixtures/authorities/linked_data/lod_sort.json +2 -1
  58. data/spec/fixtures/authorities/linked_data/lod_term_id_param_config.json +2 -1
  59. data/spec/fixtures/authorities/linked_data/lod_term_only_config.json +2 -1
  60. data/spec/fixtures/authorities/linked_data/lod_term_uri_param_config.json +2 -1
  61. data/spec/fixtures/getty-error-response.txt +10 -0
  62. data/spec/fixtures/lod_2_ranked_2_unranked.nt +17 -0
  63. data/spec/fixtures/lod_3_ranked_varying_preds.nt +16 -0
  64. data/spec/fixtures/lod_lang_search_filtering.nt +11 -0
  65. data/spec/fixtures/lod_search_with_blanknode_subjects.nt +18 -0
  66. data/spec/fixtures/lod_term_with_blanknode_objects.nt +8 -0
  67. data/spec/lib/authorities/assign_fast_spec.rb +1 -0
  68. data/spec/lib/authorities/getty/aat_spec.rb +14 -2
  69. data/spec/lib/authorities/getty/tgn_spec.rb +14 -2
  70. data/spec/lib/authorities/getty/ulan_spec.rb +14 -2
  71. data/spec/lib/authorities/linked_data/authority_service_spec.rb +2 -1
  72. data/spec/lib/authorities/linked_data/config_spec.rb +284 -5
  73. data/spec/lib/authorities/linked_data/find_term_spec.rb +3 -1
  74. data/spec/lib/authorities/linked_data/generic_authority_spec.rb +92 -42
  75. data/spec/lib/authorities/linked_data/search_config_spec.rb +67 -160
  76. data/spec/lib/authorities/linked_data/search_query_spec.rb +3 -127
  77. data/spec/lib/authorities/linked_data/term_config_spec.rb +6 -134
  78. data/spec/lib/authorities/loc_spec.rb +9 -9
  79. data/spec/lib/configuration_spec.rb +20 -7
  80. data/spec/lib/tasks/mesh.rake_spec.rb +2 -2
  81. data/spec/models/iri_template/url_config_spec.rb +102 -0
  82. data/spec/models/iri_template/variable_map_spec.rb +105 -0
  83. data/spec/models/linked_data/config/context_map_spec.rb +148 -0
  84. data/spec/models/linked_data/config/context_property_map_spec.rb +286 -0
  85. data/spec/services/iri_template_service_spec.rb +69 -0
  86. data/spec/services/linked_data/authority_url_service_spec.rb +107 -0
  87. data/spec/services/linked_data/deep_sort_service_spec.rb +260 -0
  88. data/spec/services/linked_data/graph_service_spec.rb +232 -0
  89. data/spec/services/linked_data/language_service_spec.rb +66 -0
  90. data/spec/services/linked_data/language_sort_service_spec.rb +58 -0
  91. data/spec/services/linked_data/mapper/context_mapper_service_spec.rb +137 -0
  92. data/spec/services/linked_data/mapper/graph_mapper_service_spec.rb +110 -0
  93. data/spec/services/linked_data/mapper/search_results_mapper_service_spec.rb +109 -0
  94. data/spec/spec_helper.rb +10 -2
  95. metadata +81 -11
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Qa::IriTemplateService do
4
+ let(:url_template) do
5
+ {
6
+ :'@context' => 'http://www.w3.org/ns/hydra/context.jsonld',
7
+ :'@type' => 'IriTemplate',
8
+ template: 'http://localhost/test_default/search?{?subauth}&{?query}&{?max_records}&{?language}',
9
+ variableRepresentation: 'BasicRepresentation',
10
+ mapping: [
11
+ {
12
+ :'@type' => 'IriTemplateMapping',
13
+ variable: 'query',
14
+ property: 'hydra:freetextQuery',
15
+ required: true
16
+ },
17
+ {
18
+ :'@type' => 'IriTemplateMapping',
19
+ variable: 'subauth',
20
+ property: 'hydra:freetextQuery',
21
+ required: false,
22
+ default: 'personal_names'
23
+ },
24
+ {
25
+ :'@type' => 'IriTemplateMapping',
26
+ variable: 'max_records',
27
+ property: 'hydra:freetextQuery',
28
+ required: false,
29
+ default: 20
30
+ },
31
+ {
32
+ :'@type' => 'IriTemplateMapping',
33
+ variable: 'language',
34
+ property: 'hydra:freetextQuery',
35
+ required: false,
36
+ default: 'en'
37
+ }
38
+ ]
39
+ }
40
+ end
41
+ let(:url_config) { Qa::IriTemplate::UrlConfig.new(url_template) }
42
+
43
+ describe '.build_url' do
44
+ context 'when all substitutions specified' do
45
+ let(:substitutions) do
46
+ HashWithIndifferentAccess.new(
47
+ query: 'mark twain',
48
+ subauth: 'corporate_names',
49
+ max_records: 10,
50
+ language: 'fr'
51
+ )
52
+ end
53
+
54
+ it 'returns template with substitutions' do
55
+ expected_url = 'http://localhost/test_default/search?subauth=corporate_names&query=mark twain&max_records=10&language=fr'
56
+ expect(described_class.build_url(url_config: url_config, substitutions: substitutions)).to eq expected_url
57
+ end
58
+ end
59
+
60
+ context 'when minimal substitutions specified' do
61
+ let(:substitutions) { HashWithIndifferentAccess.new(query: 'mark twain') }
62
+
63
+ it 'returns template with substitutions' do
64
+ expected_url = 'http://localhost/test_default/search?subauth=personal_names&query=mark twain&max_records=20&language=en'
65
+ expect(described_class.build_url(url_config: url_config, substitutions: substitutions)).to eq expected_url
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Qa::LinkedData::AuthorityUrlService do
4
+ let(:authority) { :OCLC_FAST }
5
+ let(:search_config) { Qa::Authorities::LinkedData::Config.new(authority).search }
6
+ let(:term_config) { Qa::Authorities::LinkedData::Config.new(authority).term }
7
+ let(:action_config) { search_config }
8
+
9
+ let(:subauthority) { nil }
10
+ let(:action) { :search }
11
+ let(:action_request) { "mark+twain" }
12
+ let(:substitutions) do
13
+ {}
14
+ end
15
+
16
+ describe '.build_url' do
17
+ context 'when authority is not registered' do
18
+ let(:authority) { :BAD_AUTHORITY }
19
+
20
+ it 'raises error' do
21
+ expected_error = Qa::InvalidLinkedDataAuthority
22
+ expected_error_message = "Unable to initialize linked data authority 'BAD_AUTHORITY'"
23
+ expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) }
24
+ .to raise_error(expected_error, expected_error_message)
25
+ end
26
+ end
27
+
28
+ # TODO: elr - currently uses the default subauthority if the one passed in isn't supported
29
+ context 'when subauthority is not supported' do
30
+ let(:subauthority) { :BAD_SUBAUTHORITY }
31
+
32
+ it 'raises error' do
33
+ skip "Pending better handling of unsupported subauthorities"
34
+ expected_error = Qa::InvalidLinkedDataAuthority
35
+ expected_error_message = "Unable to initialize linked data sub-authority BAD_SUBAUTHORITY"
36
+ expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) }
37
+ .to raise_error(expected_error, expected_error_message)
38
+ end
39
+ end
40
+
41
+ context 'when invalid action is specified' do
42
+ let(:action) { :BAD_ACTION }
43
+
44
+ it 'raises error' do
45
+ expected_error = Qa::UnsupportedAction
46
+ expected_error_message = "BAD_ACTION Not Supported - Action must be one of the supported actions (e.g. :term, :search)"
47
+ expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) }
48
+ .to raise_error(expected_error, expected_error_message)
49
+ end
50
+ end
51
+
52
+ context 'when action_request is missing' do
53
+ let(:action_request) { nil }
54
+
55
+ it 'raises error' do
56
+ expected_error = Qa::IriTemplate::MissingParameter
57
+ expected_error_message = "query is required, but missing"
58
+ expect { described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions) }
59
+ .to raise_error(expected_error, expected_error_message)
60
+ end
61
+ end
62
+
63
+ subject do
64
+ described_class.build_url(action_config: action_config, subauthority: subauthority, action: action, action_request: action_request, substitutions: substitutions)
65
+ end
66
+
67
+ context 'when no errors' do
68
+ context 'and performing search action' do
69
+ context 'and all substitutions specified' do
70
+ let(:substitutions) do
71
+ HashWithIndifferentAccess.new(
72
+ maximumRecords: 10,
73
+ language: 'fr'
74
+ )
75
+ end
76
+ let(:subauthority) { 'personal_name' }
77
+ let(:action_request) { 'mark twain' }
78
+
79
+ it 'returns template with substitutions' do
80
+ expected_url = 'http://experimental.worldcat.org/fast/search?query=oclc.personalName+all+%22mark twain%22&sortKeys=usage&maximumRecords=10'
81
+ expect(subject).to eq expected_url
82
+ end
83
+ end
84
+
85
+ context 'when no substitutions specified' do
86
+ let(:action_request) { 'mark twain' }
87
+
88
+ it 'returns template with substitutions' do
89
+ expected_url = 'http://experimental.worldcat.org/fast/search?query=cql.any+all+%22mark twain%22&sortKeys=usage&maximumRecords=20'
90
+ expect(subject).to eq expected_url
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'and performing term action' do
96
+ let(:action) { :term }
97
+ let(:action_config) { term_config }
98
+ let(:action_request) { 'n79021164' }
99
+
100
+ it 'returns template with substitutions' do
101
+ expected_url = 'http://id.worldcat.org/fast/n79021164'
102
+ expect(subject).to eq expected_url
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,260 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Qa::LinkedData::DeepSortService do
4
+ describe "#deep_sort" do
5
+ subject { described_class.new(the_array, :sort, preferred_language).sort }
6
+
7
+ let(:preferred_language) { nil }
8
+
9
+ context 'as numeric sort' do
10
+ context 'when sort values are integers' do
11
+ let(:term_1) { RDF::Literal.new(1) }
12
+ let(:term_2) { RDF::Literal.new(15) }
13
+ let(:term_3) { RDF::Literal.new(20) }
14
+ let(:the_array) do
15
+ [{ sort: [term_2] },
16
+ { sort: [term_3] },
17
+ { sort: [term_1] }]
18
+ end
19
+
20
+ it "does numeric sort" do
21
+ expected_results = [{ sort: [term_1] },
22
+ { sort: [term_2] },
23
+ { sort: [term_3] }]
24
+ expect(subject).to eq expected_results
25
+ end
26
+ end
27
+
28
+ context 'when sort values are string representations of integers' do
29
+ let(:term_1) { RDF::Literal.new('1') }
30
+ let(:term_2) { RDF::Literal.new('15') }
31
+ let(:term_3) { RDF::Literal.new('20') }
32
+ let(:the_array) do
33
+ [{ sort: [term_2] },
34
+ { sort: [term_3] },
35
+ { sort: [term_1] }]
36
+ end
37
+
38
+ it "does numeric sort" do
39
+ expected_results = [{ sort: [term_1] },
40
+ { sort: [term_2] },
41
+ { sort: [term_3] }]
42
+ expect(subject).to eq expected_results
43
+ end
44
+ end
45
+
46
+ context 'when sort values are a mix of integer representations' do
47
+ let(:term_1) { RDF::Literal.new(1) }
48
+ let(:term_2) { RDF::Literal.new('15') }
49
+ let(:term_3) { RDF::Literal.new(20) }
50
+ let(:term_4) { RDF::Literal.new('201') }
51
+ let(:the_array) do
52
+ [{ sort: [term_2] },
53
+ { sort: [term_3] },
54
+ { sort: [term_1] },
55
+ { sort: [term_4] }]
56
+ end
57
+
58
+ it "does numeric sort" do
59
+ expected_results = [{ sort: [term_1] },
60
+ { sort: [term_2] },
61
+ { sort: [term_3] },
62
+ { sort: [term_4] }]
63
+ expect(subject).to eq expected_results
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'as single value sort' do
69
+ context 'when sort values do not have the language markers' do
70
+ let(:term_1) { RDF::Literal.new("apple") }
71
+ let(:term_2) { RDF::Literal.new("Banana") }
72
+ let(:term_3) { RDF::Literal.new("carrot") }
73
+ let(:the_array) do
74
+ [{ sort: [term_2] },
75
+ { sort: [term_3] },
76
+ { sort: [term_1] }]
77
+ end
78
+
79
+ it 'does alpha sort ignoring case' do
80
+ expected_results = [{ sort: [term_1] },
81
+ { sort: [term_2] },
82
+ { sort: [term_3] }]
83
+ expect(subject).to eq expected_results
84
+ end
85
+ end
86
+
87
+ context 'when sort values all have the same language marker' do
88
+ let(:term_1) { RDF::Literal.new("apple", language: :en) }
89
+ let(:term_2) { RDF::Literal.new("Banana", language: :en) }
90
+ let(:term_3) { RDF::Literal.new("carrot", language: :en) }
91
+ let(:the_array) do
92
+ [{ sort: [term_2] },
93
+ { sort: [term_3] },
94
+ { sort: [term_1] }]
95
+ end
96
+
97
+ it 'does alpha sort ignoring case' do
98
+ expected_results = [{ sort: [term_1] },
99
+ { sort: [term_2] },
100
+ { sort: [term_3] }]
101
+ expect(subject).to eq expected_results
102
+ end
103
+ end
104
+
105
+ context 'when sort values all have different language markers and no preferred language' do
106
+ let(:term_1) { RDF::Literal.new("Kuh", language: :de) }
107
+ let(:term_2) { RDF::Literal.new("Rind", language: :de) }
108
+ let(:term_3) { RDF::Literal.new("bovine", language: :en) }
109
+ let(:term_4) { RDF::Literal.new("cow", language: :en) }
110
+ let(:term_5) { RDF::Literal.new("vache", language: :fr) }
111
+ let(:term_6) { RDF::Literal.new("mucca") }
112
+ let(:term_7) { RDF::Literal.new("vaca") }
113
+ let(:the_array) do
114
+ [{ sort: [term_5] },
115
+ { sort: [term_7] },
116
+ { sort: [term_2] },
117
+ { sort: [term_6] },
118
+ { sort: [term_1] },
119
+ { sort: [term_4] },
120
+ { sort: [term_3] }]
121
+ end
122
+
123
+ it 'does alpha sort of languages and alpha sort of terms within each language' do
124
+ expected_results = [{ sort: [term_1] },
125
+ { sort: [term_2] },
126
+ { sort: [term_3] },
127
+ { sort: [term_4] },
128
+ { sort: [term_5] },
129
+ { sort: [term_6] },
130
+ { sort: [term_7] }]
131
+ expect(subject).to eq expected_results
132
+ end
133
+ end
134
+
135
+ context 'when sort values all have different language markers and has preferred language' do
136
+ let(:preferred_language) { :en }
137
+
138
+ let(:term_1) { RDF::Literal.new("bovine", language: :en) }
139
+ let(:term_2) { RDF::Literal.new("cow", language: :en) }
140
+ let(:term_3) { RDF::Literal.new("Kuh", language: :de) }
141
+ let(:term_4) { RDF::Literal.new("Rind", language: :de) }
142
+ let(:term_5) { RDF::Literal.new("vache", language: :fr) }
143
+ let(:term_6) { RDF::Literal.new("mucca") }
144
+ let(:term_7) { RDF::Literal.new("vaca") }
145
+ let(:the_array) do
146
+ [{ sort: [term_5] },
147
+ { sort: [term_7] },
148
+ { sort: [term_2] },
149
+ { sort: [term_6] },
150
+ { sort: [term_1] },
151
+ { sort: [term_4] },
152
+ { sort: [term_3] }]
153
+ end
154
+
155
+ it 'does alpha sort of languages shifting perferred language to the front and alpha sort of terms within each language' do
156
+ expected_results = [{ sort: [term_1] },
157
+ { sort: [term_2] },
158
+ { sort: [term_3] },
159
+ { sort: [term_4] },
160
+ { sort: [term_5] },
161
+ { sort: [term_6] },
162
+ { sort: [term_7] }]
163
+ expect(subject).to eq expected_results
164
+ end
165
+ end
166
+ end
167
+
168
+ context 'as multiple value sort' do
169
+ context 'when all the terms in both lists have no language markers' do
170
+ let(:term_1) { RDF::Literal.new("apple") }
171
+ let(:term_2) { RDF::Literal.new("Banana") }
172
+ let(:term_3) { RDF::Literal.new("carrot") }
173
+ let(:term_4) { RDF::Literal.new("Woof Woof") }
174
+ let(:the_array) do
175
+ [{ sort: [term_2, term_1] },
176
+ { sort: [term_3, term_2] },
177
+ { sort: [term_1, term_3, term_4, term_2] }]
178
+ end
179
+
180
+ it 'does alpha sort ignoring case' do
181
+ expected_results = [{ sort: [term_1, term_2] },
182
+ { sort: [term_1, term_2, term_3, term_4] },
183
+ { sort: [term_2, term_3] }]
184
+ expect(subject).to eq expected_results
185
+ end
186
+ end
187
+
188
+ context 'when all the terms in both lists have the same language markers' do
189
+ let(:term_1) { RDF::Literal.new("bovine", language: :en) }
190
+ let(:term_2) { RDF::Literal.new("Cow", language: :en) }
191
+ let(:term_3) { RDF::Literal.new("hefer", language: :en) }
192
+ let(:term_4) { RDF::Literal.new("Moo Moo", language: :en) }
193
+ let(:the_array) do
194
+ [{ sort: [term_2, term_1] },
195
+ { sort: [term_3, term_2] },
196
+ { sort: [term_1, term_3, term_4, term_2] }]
197
+ end
198
+
199
+ it 'does alpha sort ignoring case' do
200
+ expected_results = [{ sort: [term_1, term_2] },
201
+ { sort: [term_1, term_2, term_3, term_4] },
202
+ { sort: [term_2, term_3] }]
203
+ expect(subject).to eq expected_results
204
+ end
205
+ end
206
+
207
+ context 'when terms exist in both lists that match the preferred language' do
208
+ let(:preferred_language) { :en }
209
+
210
+ let(:term_1) { RDF::Literal.new("bovine", language: :en) }
211
+ let(:term_2) { RDF::Literal.new("cow", language: :en) }
212
+ let(:term_3) { RDF::Literal.new("heffer", language: :en) }
213
+ let(:term_4) { RDF::Literal.new("Kuh", language: :de) }
214
+ let(:term_5) { RDF::Literal.new("Rind", language: :de) }
215
+ let(:term_6) { RDF::Literal.new("vache", language: :fr) }
216
+ let(:term_7) { RDF::Literal.new("mucca") }
217
+ let(:term_8) { RDF::Literal.new("vaca") }
218
+ let(:the_array) do
219
+ [{ sort: [term_4, term_5, term_2, term_1] },
220
+ { sort: [term_3, term_5, term_4, term_7, term_2] },
221
+ { sort: [term_1, term_6, term_4, term_2, term_5, term_8, term_7, term_3] }]
222
+ end
223
+
224
+ it 'does alpha sort ignoring case on the preferred language only' do
225
+ expected_results = [{ sort: [term_1, term_2, term_4, term_5] },
226
+ { sort: [term_1, term_2, term_3, term_4, term_5, term_6, term_7, term_8] },
227
+ { sort: [term_2, term_3, term_4, term_5, term_7] }]
228
+ expect(subject).to eq expected_results
229
+ end
230
+ end
231
+
232
+ context 'when there is complete chaos under heaven and all things are rotten' do
233
+ let(:preferred_language) { :en }
234
+
235
+ let(:term_1) { RDF::Literal.new("Kuh", language: :de) }
236
+ let(:term_2) { RDF::Literal.new("Rind", language: :de) }
237
+ let(:term_3) { RDF::Literal.new("hembra", language: :es) }
238
+ let(:term_4) { RDF::Literal.new("res vacuna", language: :es) }
239
+ let(:term_5) { RDF::Literal.new("vaca", language: :es) }
240
+ let(:term_6) { RDF::Literal.new("vache", language: :fr) }
241
+ let(:term_7) { RDF::Literal.new("ko") }
242
+ let(:term_8) { RDF::Literal.new("mucca") }
243
+ let(:the_array) do
244
+ [{ sort: [term_4, term_5, term_2, term_1] }, # res vacuna, vaca, Rind, Kuh
245
+ { sort: [term_3, term_5, term_4, term_7, term_2] }, # hembra, vaca, res vacuna, ko, Rind
246
+ { sort: [term_3, term_6, term_5] }, # hembra, vache, vaca
247
+ { sort: [term_1, term_6, term_4, term_2, term_5, term_8, term_7, term_3] }] # Kuh, vache, res vacuna, Rind, vaca, mucca, ko, hembra
248
+ end
249
+
250
+ it 'does alpha sort ignoring case on the preferred language only' do
251
+ expected_results = [{ sort: [term_1, term_2, term_3, term_4, term_5, term_6, term_7, term_8] }, # Kuh, Rind, hembra, res vacuna, vaca, vache, ko, mucca
252
+ { sort: [term_2, term_3, term_4, term_5, term_7] }, # Kuh, Rind, res vacuna, vaca
253
+ { sort: [term_3, term_5, term_6] }, # hembra, vaca, vache
254
+ { sort: [term_1, term_2, term_4, term_5] }] # Rind, hembra, res vacuna, vaca, mucca
255
+ expect(subject).to eq expected_results
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,232 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Qa::LinkedData::GraphService do
4
+ describe '.load_graph' do
5
+ subject { described_class.load_graph(url: url) }
6
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
7
+
8
+ context 'when graph can be loaded' do
9
+ before do
10
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
11
+ .to_return(status: 200, body: webmock_fixture('lod_oclc_all_query_3_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
12
+ end
13
+
14
+ it 'builds a graph with many statements' do
15
+ expect(subject).to be_kind_of RDF::Graph
16
+ expect(subject.statements.size).to be > 10
17
+ end
18
+ end
19
+
20
+ context 'when term is not found' do
21
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
22
+
23
+ before do
24
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
25
+ .to_return(status: 404)
26
+ end
27
+
28
+ it 'raises error' do
29
+ expect { described_class.load_graph(url: url) }.to raise_error(Qa::TermNotFound, "#{url} Not Found - Term may not exist at LOD Authority. (HTTPNotFound - 404)")
30
+ end
31
+ end
32
+
33
+ context 'when service error' do
34
+ subject { described_class.load_graph(url: url) }
35
+
36
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
37
+ let(:uri) { URI(url) }
38
+
39
+ before do
40
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
41
+ .to_return(status: 500)
42
+ end
43
+
44
+ it 'raises error' do
45
+ expect { subject }.to raise_error(Qa::ServiceError, "#{uri.hostname} on port #{uri.port} is not responding. Try again later. (HTTPServerError - 500)")
46
+ end
47
+ end
48
+
49
+ context 'when service unavailable' do
50
+ subject { described_class.load_graph(url: url) }
51
+
52
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
53
+ let(:uri) { URI(url) }
54
+
55
+ before do
56
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
57
+ .to_return(status: 503)
58
+ end
59
+
60
+ it 'raises error' do
61
+ expect { subject }.to raise_error(Qa::ServiceUnavailable, "#{uri.hostname} on port #{uri.port} is not responding. Try again later. (HTTPServiceUnavailable - 503)")
62
+ end
63
+ end
64
+
65
+ context "when error isn't specifically handled" do
66
+ subject { described_class.load_graph(url: url) }
67
+
68
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
69
+ let(:uri) { URI(url) }
70
+
71
+ before do
72
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
73
+ .to_return(status: 504)
74
+ end
75
+
76
+ it 'raises error' do
77
+ expect { subject }.to raise_error(Qa::ServiceError, "Unknown error for #{uri.hostname} on port #{uri.port}. Try again later. (Cause - <#{url}>: (504))")
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '.filter' do
83
+ context 'with language filter' do
84
+ subject { described_class.filter(graph: graph, language: language) }
85
+
86
+ let(:url) { 'http://authority.with.language/search?query=foo' }
87
+ let(:graph) { described_class.load_graph(url: url) }
88
+
89
+ let(:en_dried_milk) { RDF::Literal.new("dried milk", language: :en) }
90
+ let(:fr_dried_milk) { RDF::Literal.new("lait en poudre", language: :fr) }
91
+ let(:de_dried_milk) { RDF::Literal.new("getrocknete Milch", language: :de) }
92
+
93
+ let(:en_buttermilk) { RDF::Literal.new("buttermilk", language: :en) }
94
+ let(:fr_buttermilk) { RDF::Literal.new("Babeurre", language: :fr) }
95
+ let(:de_buttermilk) { RDF::Literal.new("Buttermilch", language: :de) }
96
+
97
+ let(:en_condensed_milk) { RDF::Literal.new("condensed milk", language: :en) }
98
+ let(:fr_condensed_milk) { RDF::Literal.new("lait condensé", language: :fr) }
99
+ let(:de_condensed_milk) { RDF::Literal.new("Kondensmilch", language: :de) }
100
+
101
+ before do
102
+ stub_request(:get, 'http://authority.with.language/search?query=foo')
103
+ .to_return(status: 200, body: webmock_fixture('lod_lang_search_enfrde.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
104
+ end
105
+
106
+ context 'when one language passed in' do
107
+ let(:language) { [:fr] }
108
+
109
+ it 'returns the graph with only the French subset of triples' do
110
+ expect(subject.has_object?(en_dried_milk)).to be false
111
+ expect(subject.has_object?(en_buttermilk)).to be false
112
+ expect(subject.has_object?(en_condensed_milk)).to be false
113
+
114
+ expect(subject.has_object?(fr_dried_milk)).to be true
115
+ expect(subject.has_object?(fr_buttermilk)).to be true
116
+ expect(subject.has_object?(fr_condensed_milk)).to be true
117
+
118
+ expect(subject.has_object?(de_dried_milk)).to be false
119
+ expect(subject.has_object?(de_buttermilk)).to be false
120
+ expect(subject.has_object?(de_condensed_milk)).to be false
121
+ end
122
+ end
123
+
124
+ context "when mix of language markers on graph statements" do
125
+ let(:nomarker_de_buttermilk) { RDF::Literal.new("Buttermilch") }
126
+ let(:language) { [:fr] }
127
+
128
+ before do
129
+ stub_request(:get, 'http://authority.with.language/search?query=foo')
130
+ .to_return(status: 200, body: webmock_fixture('lod_lang_search_filtering.nt'), headers: { 'Content-Type' => 'application/n-triples' })
131
+ end
132
+
133
+ it 'filters down to the expected size' do
134
+ expect(graph.size).to eq 11
135
+ expect(subject.size).to eq 8
136
+ end
137
+
138
+ it 'the graph includes triples where object has the targeted language marker (e.g. :fr)' do
139
+ expect(subject.has_object?(fr_dried_milk)).to be true
140
+ expect(subject.has_object?(fr_buttermilk)).to be true
141
+ end
142
+
143
+ it 'the graph includes triples where object does not have a language marker' do
144
+ expect(subject.has_object?(nomarker_de_buttermilk)).to be true
145
+ end
146
+
147
+ it "the graph includes triples regardless of language when there are no object's for the predicate that have the targeted language marker" do
148
+ expect(subject.has_object?(en_condensed_milk)).to be true
149
+ expect(subject.has_object?(de_condensed_milk)).to be true
150
+ end
151
+
152
+ it 'filters out the rest' do
153
+ expect(subject.has_object?(en_dried_milk)).to be false
154
+ expect(subject.has_object?(en_buttermilk)).to be false
155
+ expect(subject.has_object?(de_dried_milk)).to be false
156
+ end
157
+ end
158
+
159
+ context 'when multiple languages passed in' do
160
+ let(:language) { [:en, :fr] }
161
+
162
+ it 'returns the graph with English and French subset of triples' do
163
+ expect(subject.has_object?(en_dried_milk)).to be true
164
+ expect(subject.has_object?(en_buttermilk)).to be true
165
+ expect(subject.has_object?(en_condensed_milk)).to be true
166
+
167
+ expect(subject.has_object?(fr_dried_milk)).to be true
168
+ expect(subject.has_object?(fr_buttermilk)).to be true
169
+ expect(subject.has_object?(fr_condensed_milk)).to be true
170
+
171
+ expect(subject.has_object?(de_dried_milk)).to be false
172
+ expect(subject.has_object?(de_buttermilk)).to be false
173
+ expect(subject.has_object?(de_condensed_milk)).to be false
174
+ end
175
+ end
176
+ end
177
+
178
+ context 'with filter out subject blanknodes' do
179
+ subject { described_class.filter(graph: graph, remove_blanknode_subjects: true) }
180
+
181
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
182
+ let(:graph) { described_class.load_graph(url: url) }
183
+
184
+ before do
185
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
186
+ .to_return(status: 200, body: webmock_fixture('lod_search_with_blanknode_subjects.nt'), headers: { 'Content-Type' => 'application/n-triples' })
187
+ end
188
+
189
+ it 'removes statements where the subject is a blanknode' do
190
+ expect(graph.size).to be 18
191
+ expect(subject.size).to be 12
192
+ end
193
+ end
194
+ end
195
+
196
+ describe '.object_values' do
197
+ subject { described_class.object_values(graph: graph, subject: subject_uri, predicate: predicate_uri) }
198
+
199
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
200
+ let(:graph) { described_class.load_graph(url: url) }
201
+ let(:subject_uri) { RDF::URI('http://id.worldcat.org/fast/530369') }
202
+ let(:predicate_uri) { RDF::URI('http://schema.org/sameAs') }
203
+
204
+ before do
205
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
206
+ .to_return(status: 200, body: webmock_fixture('lod_oclc_all_query_3_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
207
+ end
208
+
209
+ it 'returns all values for the subject-predicate pair' do
210
+ expect(subject.size).to be 2
211
+ expect(subject.map(&:to_s)).to match_array ['http://id.loc.gov/authorities/names/n79021621', 'https://viaf.org/viaf/126293486']
212
+ end
213
+ end
214
+
215
+ describe '.deep_copy' do
216
+ subject { described_class.object_values(graph: graph, subject: subject_uri, predicate: predicate_uri) }
217
+
218
+ let(:url) { 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage' }
219
+ let(:graph) { described_class.load_graph(url: url) }
220
+ let(:copied_graph) { described_class.deep_copy(graph: graph) }
221
+
222
+ before do
223
+ stub_request(:get, 'http://experimental.worldcat.org/fast/search?maximumRecords=3&query=cql.any%20all%20%22cornell%22&sortKeys=usage')
224
+ .to_return(status: 200, body: webmock_fixture('lod_oclc_all_query_3_results.rdf.xml'), headers: { 'Content-Type' => 'application/rdf+xml' })
225
+ end
226
+
227
+ it 'returns a copy of the graph' do
228
+ expect(copied_graph).to be_kind_of RDF::Graph
229
+ expect(copied_graph.statements.count).to eq graph.statements.count
230
+ end
231
+ end
232
+ end