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.
- checksums.yaml +5 -5
- data/README.md +16 -548
- data/app/controllers/qa/linked_data_terms_controller.rb +64 -42
- data/app/controllers/qa/terms_controller.rb +14 -6
- data/app/models/qa/iri_template/url_config.rb +47 -0
- data/app/models/qa/iri_template/variable_map.rb +62 -0
- data/app/models/qa/linked_data/config/context_map.rb +77 -0
- data/app/models/qa/linked_data/config/context_property_map.rb +144 -0
- data/app/models/qa/linked_data/config/helper.rb +34 -0
- data/app/services/qa/iri_template_service.rb +31 -0
- data/{lib/qa/authorities → app/services/qa}/linked_data/authority_service.rb +3 -4
- data/app/services/qa/linked_data/authority_url_service.rb +48 -0
- data/app/services/qa/linked_data/deep_sort_service.rb +238 -0
- data/app/services/qa/linked_data/graph_service.rb +106 -0
- data/app/services/qa/linked_data/language_service.rb +30 -0
- data/app/services/qa/linked_data/language_sort_service.rb +81 -0
- data/app/services/qa/linked_data/mapper/context_mapper_service.rb +59 -0
- data/app/services/qa/linked_data/mapper/graph_mapper_service.rb +40 -0
- data/app/services/qa/linked_data/mapper/search_results_mapper_service.rb +70 -0
- data/config/authorities/linked_data/loc.json +5 -2
- data/config/authorities/linked_data/oclc_fast.json +3 -2
- data/config/initializers/linked_data_authorities.rb +1 -1
- data/config/locales/qa.en.yml +9 -0
- data/lib/generators/qa/install/templates/config/initializers/qa.rb +4 -0
- data/lib/qa.rb +8 -0
- data/lib/qa/authorities/assign_fast/generic_authority.rb +1 -1
- data/lib/qa/authorities/base.rb +0 -11
- data/lib/qa/authorities/crossref/generic_authority.rb +1 -1
- data/lib/qa/authorities/geonames.rb +1 -1
- data/lib/qa/authorities/getty/aat.rb +7 -2
- data/lib/qa/authorities/getty/tgn.rb +7 -2
- data/lib/qa/authorities/getty/ulan.rb +7 -2
- data/lib/qa/authorities/linked_data.rb +0 -1
- data/lib/qa/authorities/linked_data/config.rb +29 -28
- data/lib/qa/authorities/linked_data/config/search_config.rb +21 -79
- data/lib/qa/authorities/linked_data/config/term_config.rb +7 -77
- data/lib/qa/authorities/linked_data/find_term.rb +25 -17
- data/lib/qa/authorities/linked_data/generic_authority.rb +6 -5
- data/lib/qa/authorities/linked_data/rdf_helper.rb +6 -73
- data/lib/qa/authorities/linked_data/search_query.rb +54 -101
- data/lib/qa/authorities/loc/generic_authority.rb +4 -4
- data/lib/qa/authorities/web_service_base.rb +1 -8
- data/lib/qa/configuration.rb +7 -0
- data/lib/qa/version.rb +1 -1
- data/lib/tasks/mesh.rake +19 -18
- data/spec/controllers/linked_data_terms_controller_spec.rb +51 -1
- data/spec/controllers/terms_controller_spec.rb +15 -15
- data/spec/fixtures/authorities/linked_data/lod_encoding_config.json +2 -1
- data/spec/fixtures/authorities/linked_data/lod_full_config.json +56 -2
- data/spec/fixtures/authorities/linked_data/lod_full_config_1_0.json +164 -0
- data/spec/fixtures/authorities/linked_data/lod_lang_defaults.json +5 -4
- data/spec/fixtures/authorities/linked_data/lod_lang_multi_defaults.json +3 -2
- data/spec/fixtures/authorities/linked_data/lod_lang_no_defaults.json +3 -2
- data/spec/fixtures/authorities/linked_data/lod_lang_param.json +3 -2
- data/spec/fixtures/authorities/linked_data/lod_min_config.json +3 -2
- data/spec/fixtures/authorities/linked_data/lod_search_only_config.json +2 -1
- data/spec/fixtures/authorities/linked_data/lod_sort.json +2 -1
- data/spec/fixtures/authorities/linked_data/lod_term_id_param_config.json +2 -1
- data/spec/fixtures/authorities/linked_data/lod_term_only_config.json +2 -1
- data/spec/fixtures/authorities/linked_data/lod_term_uri_param_config.json +2 -1
- data/spec/fixtures/getty-error-response.txt +10 -0
- data/spec/fixtures/lod_2_ranked_2_unranked.nt +17 -0
- data/spec/fixtures/lod_3_ranked_varying_preds.nt +16 -0
- data/spec/fixtures/lod_lang_search_filtering.nt +11 -0
- data/spec/fixtures/lod_search_with_blanknode_subjects.nt +18 -0
- data/spec/fixtures/lod_term_with_blanknode_objects.nt +8 -0
- data/spec/lib/authorities/assign_fast_spec.rb +1 -0
- data/spec/lib/authorities/getty/aat_spec.rb +14 -2
- data/spec/lib/authorities/getty/tgn_spec.rb +14 -2
- data/spec/lib/authorities/getty/ulan_spec.rb +14 -2
- data/spec/lib/authorities/linked_data/authority_service_spec.rb +2 -1
- data/spec/lib/authorities/linked_data/config_spec.rb +284 -5
- data/spec/lib/authorities/linked_data/find_term_spec.rb +3 -1
- data/spec/lib/authorities/linked_data/generic_authority_spec.rb +92 -42
- data/spec/lib/authorities/linked_data/search_config_spec.rb +67 -160
- data/spec/lib/authorities/linked_data/search_query_spec.rb +3 -127
- data/spec/lib/authorities/linked_data/term_config_spec.rb +6 -134
- data/spec/lib/authorities/loc_spec.rb +9 -9
- data/spec/lib/configuration_spec.rb +20 -7
- data/spec/lib/tasks/mesh.rake_spec.rb +2 -2
- data/spec/models/iri_template/url_config_spec.rb +102 -0
- data/spec/models/iri_template/variable_map_spec.rb +105 -0
- data/spec/models/linked_data/config/context_map_spec.rb +148 -0
- data/spec/models/linked_data/config/context_property_map_spec.rb +286 -0
- data/spec/services/iri_template_service_spec.rb +69 -0
- data/spec/services/linked_data/authority_url_service_spec.rb +107 -0
- data/spec/services/linked_data/deep_sort_service_spec.rb +260 -0
- data/spec/services/linked_data/graph_service_spec.rb +232 -0
- data/spec/services/linked_data/language_service_spec.rb +66 -0
- data/spec/services/linked_data/language_sort_service_spec.rb +58 -0
- data/spec/services/linked_data/mapper/context_mapper_service_spec.rb +137 -0
- data/spec/services/linked_data/mapper/graph_mapper_service_spec.rb +110 -0
- data/spec/services/linked_data/mapper/search_results_mapper_service_spec.rb +109 -0
- data/spec/spec_helper.rb +10 -2
- 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
|