qa 3.1.0 → 4.0.0.rc1

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