rdf-ldp 0.9.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,337 @@
1
+ require 'rdf/ldp/spec/rdf_source'
2
+
3
+ shared_examples 'a Container' do
4
+ it_behaves_like 'an RDFSource'
5
+
6
+ let(:uri) { RDF::URI('http://ex.org/moomin') }
7
+ subject { described_class.new(uri) }
8
+
9
+ it { is_expected.to be_container }
10
+
11
+ describe '#container_class' do
12
+ it 'returns a uri' do
13
+ expect(subject.container_class).to be_a RDF::URI
14
+ end
15
+ end
16
+
17
+ describe '#containment_triples' do
18
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
19
+
20
+ it 'returns a uri' do
21
+ subject.add_containment_triple(resource)
22
+ expect(subject.containment_triples)
23
+ .to contain_exactly(an_instance_of(RDF::Statement))
24
+ end
25
+ end
26
+
27
+ describe '#add' do
28
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
29
+ before { subject.create(StringIO.new, 'application/n-triples') }
30
+
31
+ it 'returns self' do
32
+ expect(subject.add(resource)).to eq subject
33
+ end
34
+
35
+ it 'containment triple is added to graph' do
36
+ expect(subject.add(resource).graph)
37
+ .to include subject.make_containment_triple(resource)
38
+ end
39
+ end
40
+
41
+ describe '#add_containment_triple' do
42
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
43
+
44
+ it 'returns self' do
45
+ expect(subject.add_containment_triple(resource)).to eq subject
46
+ end
47
+
48
+ it 'containment triple is added to graph' do
49
+ expect(subject.add_containment_triple(resource).graph)
50
+ .to include subject.make_containment_triple(resource)
51
+ end
52
+ end
53
+
54
+ describe '#remove_containment_triple' do
55
+ before { subject.add_containment_triple(resource) }
56
+
57
+ let(:resource) { RDF::URI('http://ex.org/mymble') }
58
+
59
+ it 'returns self' do
60
+ expect(subject.remove_containment_triple(resource)).to eq subject
61
+ end
62
+
63
+ it 'membership triple is added to graph' do
64
+ expect(subject.remove_containment_triple(resource).graph)
65
+ .not_to include subject.make_containment_triple(resource)
66
+ end
67
+
68
+ it 'updates last_modified for container' do
69
+ expect { subject.remove_containment_triple(resource) }
70
+ .to change { subject.last_modified }
71
+ end
72
+
73
+ it 'updates etag for container' do
74
+ expect { subject.remove_containment_triple(resource) }
75
+ .to change { subject.etag }
76
+ end
77
+ end
78
+
79
+ describe '#make_containment_triple' do
80
+ let(:resource) { uri / 'papa' }
81
+
82
+ it 'returns a statement' do
83
+ expect(subject.make_containment_triple(resource)).to be_a RDF::Statement
84
+ end
85
+
86
+ it 'statement subject *or* object is #subject_uri' do
87
+ sub = subject.make_containment_triple(resource).subject
88
+ obj = subject.make_containment_triple(resource).object
89
+ expect([sub, obj]).to include subject.subject_uri
90
+ end
91
+
92
+ it 'converts Resource classes to URI' do
93
+ sub = subject.make_containment_triple(subject).subject
94
+ obj = subject.make_containment_triple(subject).object
95
+ expect([sub, obj]).to include subject.subject_uri
96
+ end
97
+ end
98
+
99
+ describe '#request' do
100
+ let(:graph) { RDF::Graph.new }
101
+
102
+ let(:env) do
103
+ { 'rack.input' => StringIO.new(graph.dump(:ntriples)),
104
+ 'CONTENT_TYPE' => 'application/n-triples' }
105
+ end
106
+
107
+ let(:statement) do
108
+ RDF::Statement(subject.subject_uri,
109
+ RDF::Vocab::LDP.contains,
110
+ 'moomin')
111
+ end
112
+
113
+ context 'with :PATCH',
114
+ if: described_class.private_method_defined?(:patch) do
115
+
116
+ it 'raises conflict error when editing containment triples' do
117
+ patch_statement = statement.clone
118
+ patch_statement.object = 'snorkmaiden'
119
+ patch = '@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .' \
120
+ "\n\nAdd { #{statement.subject.to_base} " \
121
+ "#{statement.predicate.to_base} #{statement.object.to_base} } ."
122
+ env = { 'CONTENT_TYPE' => 'text/ldpatch',
123
+ 'rack.input' => StringIO.new(patch) }
124
+
125
+ expect { subject.request(:patch, 200, {}, env) }
126
+ .to raise_error RDF::LDP::Conflict
127
+ end
128
+ end
129
+
130
+ context 'with :PUT',
131
+ if: described_class.private_method_defined?(:put) do
132
+ context 'when PUTing containment triples' do
133
+ it 'when creating a resource raises a Conflict error' do
134
+ graph << statement
135
+
136
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
137
+ .to raise_error RDF::LDP::Conflict
138
+ end
139
+
140
+ it 'when resource exists raises a Conflict error' do
141
+ subject.create(StringIO.new, 'application/n-triples')
142
+ graph << statement
143
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
144
+ .to raise_error RDF::LDP::Conflict
145
+ end
146
+
147
+ it 'can put existing containment triple' do
148
+ subject.create(StringIO.new, 'application/n-triples')
149
+ subject.graph << statement
150
+ graph << statement
151
+ expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env).first)
152
+ .to eq 200
153
+ end
154
+
155
+ it 'writes data when putting existing containment triple' do
156
+ subject.create(StringIO.new, 'application/n-triples')
157
+ subject.graph << statement
158
+ graph << statement
159
+
160
+ new_st = RDF::Statement(RDF::URI('http://example.org/new_moomin'),
161
+ RDF::Vocab::DC.title,
162
+ 'moomin')
163
+ graph << new_st
164
+ expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env).last.graph)
165
+ .to have_statement new_st
166
+ end
167
+
168
+ it 'raises conflict error when without existing containment triples' do
169
+ subject.create(StringIO.new, 'application/n-triples')
170
+ subject.graph << statement
171
+ expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
172
+ .to raise_error RDF::LDP::Conflict
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'when POST is implemented',
178
+ if: described_class.private_method_defined?(:post) do
179
+ let(:graph) { RDF::Graph.new }
180
+ before { subject.create(StringIO.new, 'application/n-triples') }
181
+
182
+ let(:env) do
183
+ { 'rack.input' => StringIO.new(graph.dump(:ntriples)),
184
+ 'CONTENT_TYPE' => 'application/n-triples' }
185
+ end
186
+
187
+ it 'returns status 201' do
188
+ expect(subject.request(:POST, 200, {}, env).first).to eq 201
189
+ end
190
+
191
+ it 'gives created resource as body' do
192
+ expect(subject.request(:POST, 200, {}, env).last)
193
+ .to be_a RDF::LDP::Resource
194
+ end
195
+
196
+ it 'generates an id' do
197
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
198
+ .to be_starts_with subject.subject_uri.to_s
199
+ end
200
+
201
+ it 'adds containment statement to resource' do
202
+ expect { subject.request(:POST, 200, {}, env) }
203
+ .to change { subject.containment_triples.count }.from(0).to(1)
204
+ end
205
+
206
+ it 'updates last_modified for container' do
207
+ expect { subject.request(:POST, 200, {}, env) }
208
+ .to change { subject.last_modified }
209
+ end
210
+
211
+ it 'updates etag for container' do
212
+ expect { subject.request(:POST, 200, {}, env) }
213
+ .to change { subject.etag }
214
+ end
215
+
216
+ context 'with Container interaction model' do
217
+ it 'creates a basic container' do
218
+ env['HTTP_LINK'] = "<#{RDF::LDP::Container.to_uri}>;rel=\"type\""
219
+ expect(subject.request(:POST, 200, {}, env).last)
220
+ .to be_a RDF::LDP::Container
221
+ end
222
+
223
+ context 'BasicContainer' do
224
+ it 'creates a basic container' do
225
+ env['HTTP_LINK'] =
226
+ '<http://www.w3.org/ns/ldp#BasicContainer>;rel="type"'
227
+ expect(subject.request(:POST, 200, {}, env).last)
228
+ .to be_a RDF::LDP::Container
229
+ end
230
+ end
231
+
232
+ context 'DirectContainer' do
233
+ it 'creates a direct container' do
234
+ env['HTTP_LINK'] =
235
+ "<#{RDF::LDP::DirectContainer.to_uri}>;rel=\"type\""
236
+
237
+ expect(subject.request(:POST, 200, {}, env).last)
238
+ .to be_a RDF::LDP::DirectContainer
239
+ end
240
+ end
241
+
242
+ context 'IndirectContainer' do
243
+ it 'creates a indirect container' do
244
+ env['HTTP_LINK'] =
245
+ "<#{RDF::LDP::IndirectContainer.to_uri}>;rel=\"type\""
246
+
247
+ expect(subject.request(:POST, 200, {}, env).last)
248
+ .to be_a RDF::LDP::IndirectContainer
249
+ end
250
+ end
251
+ end
252
+
253
+ context 'with a Slug' do
254
+ it 'creates resource with Slug' do
255
+ env['HTTP_SLUG'] = 'snork'
256
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
257
+ .to eq subject.subject_uri / env['HTTP_SLUG']
258
+ end
259
+
260
+ it 'mints a uri when empty Slug is given' do
261
+ env['HTTP_SLUG'] = ''
262
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
263
+ .to be_starts_with subject.subject_uri
264
+ end
265
+
266
+ it 'raises a 409 Conflict when slug is already taken' do
267
+ env['HTTP_SLUG'] = 'snork'
268
+ subject.request(:POST, 200, {}, env)
269
+
270
+ expect { subject.request(:POST, 200, {}, env) }
271
+ .to raise_error RDF::LDP::Conflict
272
+ end
273
+
274
+ it 'raises a 409 Conflict when slug is already taken but destroyed' do
275
+ env['HTTP_SLUG'] = 'snork'
276
+ created = subject.request(:POST, 200, {}, env).last
277
+ allow(created).to receive(:destroyed?).and_return true
278
+
279
+ expect { subject.request(:POST, 200, {}, env) }
280
+ .to raise_error RDF::LDP::Conflict
281
+ end
282
+
283
+ it 'raises a 406 NotAcceptable if slug has a uri fragment `#`' do
284
+ env['HTTP_SLUG'] = 'snork#maiden'
285
+
286
+ expect { subject.request(:POST, 200, {}, env) }
287
+ .to raise_error RDF::LDP::NotAcceptable
288
+ end
289
+
290
+ it 'url-encodes Slug' do
291
+ env['HTTP_SLUG'] = 'snork maiden'
292
+ expect(subject.request(:POST, 200, {}, env).last.subject_uri)
293
+ .to eq subject.subject_uri / 'snork%20maiden'
294
+ end
295
+ end
296
+
297
+ context 'with graph content' do
298
+ before do
299
+ graph << RDF::Statement(uri, RDF::Vocab::DC.title, 'moomin')
300
+ graph << RDF::Statement(RDF::Node.new, RDF::Vocab::DC.creator, 'tove')
301
+ graph <<
302
+ RDF::Statement(RDF::Node.new, RDF.type, RDF::Vocab::FOAF.Person)
303
+ end
304
+
305
+ it 'parses graph into created resource' do
306
+ expect(subject.request(:POST, 200, {}, env).last.to_response)
307
+ .to be_isomorphic_with graph
308
+ end
309
+
310
+ it 'adds a Location header' do
311
+ expect(subject.request(:POST, 200, {}, env)[1]['Location'])
312
+ .to start_with subject.subject_uri.to_s
313
+ end
314
+
315
+ context 'with quads' do
316
+ let(:graph) do
317
+ RDF::Graph.new(graph_name: subject.subject_uri,
318
+ data: RDF::Repository.new)
319
+ end
320
+
321
+ let(:env) do
322
+ { 'rack.input' => StringIO.new(graph.dump(:nquads)),
323
+ 'CONTENT_TYPE' => 'application/n-quads' }
324
+ end
325
+
326
+ it 'parses graph into created resource without regard for context' do
327
+ context_free_graph = RDF::Graph.new
328
+ context_free_graph << graph.statements
329
+
330
+ expect(subject.request(:POST, 200, {}, env).last.to_response)
331
+ .to be_isomorphic_with context_free_graph
332
+ end
333
+ end
334
+ end
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,245 @@
1
+ require 'rdf/ldp/spec/container'
2
+
3
+ # @todo: make this set of examples less opinionated about #add behavior.
4
+ # Break #add tests into another group shared between DirectContainer &
5
+ # IndirectContainer. This way other implementations can use these specs
6
+ # but make different intrepretations of loose parts in the LDP spec.
7
+ shared_examples 'a DirectContainer' do
8
+ it_behaves_like 'a Container'
9
+
10
+ let(:uri) { RDF::URI('http://ex.org/moomin') }
11
+ let(:repo) { RDF::Repository.new }
12
+ subject { described_class.new(uri, repo) }
13
+
14
+ let(:has_member_statement) do
15
+ RDF::Statement(subject.subject_uri,
16
+ RDF::Vocab::LDP.hasMemberRelation,
17
+ RDF::Vocab::DC.hasPart)
18
+ end
19
+
20
+ let(:is_member_of_statement) do
21
+ RDF::Statement(subject.subject_uri,
22
+ RDF::Vocab::LDP.isMemberOfRelation,
23
+ RDF::Vocab::DC.isPartOf)
24
+ end
25
+
26
+ describe '#add' do
27
+ let(:added) { described_class.new(added_uri, repo) }
28
+ let(:added_uri) { RDF::URI('http://ex.org/too-ticky') }
29
+
30
+ context 'when the membership resource does not exist' do
31
+ before do
32
+ subject.create(StringIO.new, 'application/n-triples')
33
+
34
+ subject.graph.update has_member_statement
35
+ subject.graph.update RDF::Statement(subject.subject_uri,
36
+ RDF::Vocab::LDP.membershipResource,
37
+ membership_resource_uri)
38
+ end
39
+
40
+ let(:membership_resource_uri) do
41
+ RDF::URI('http://example.com/moomin_resource')
42
+ end
43
+
44
+ it 'adds membership triple to the container' do
45
+ expect { subject.add(added) }
46
+ .to change { subject.graph.statements }
47
+ .to include subject.make_membership_triple(added_uri)
48
+ end
49
+ end
50
+
51
+ context 'when the membership resource exists' do
52
+ before { subject.create(StringIO.new, 'application/n-triples') }
53
+
54
+ it 'adds membership triple to container' do
55
+ expect(subject.add(added).graph)
56
+ .to have_statement subject.make_membership_triple(added_uri)
57
+ end
58
+
59
+ it 'updates last_modified for membership resource' do
60
+ expect { subject.add(added).graph }
61
+ .to change { subject.last_modified }
62
+ end
63
+
64
+ it 'updates etag for for membership resource' do
65
+ expect { subject.add(added).graph }
66
+ .to change { subject.etag }
67
+ end
68
+
69
+ it 'adds membership triple to container for custom membership resource' do
70
+ repo = RDF::Repository.new
71
+ subject = described_class.new(uri, repo)
72
+ mem_rs = RDF::LDP::RDFSource.new(RDF::URI('http://ex.org/mymble'),
73
+ repo)
74
+
75
+ g = RDF::Graph.new << RDF::Statement(subject.subject_uri,
76
+ RDF::Vocab::LDP.membershipResource,
77
+ mem_rs.subject_uri)
78
+
79
+ subject.create(StringIO.new(g.dump(:ntriples)), 'application/n-triples')
80
+ mem_rs.create(StringIO.new, 'application/n-triples')
81
+
82
+ subject.add(added)
83
+
84
+ expect(subject.graph)
85
+ .to have_statement subject.make_membership_triple(added_uri)
86
+ end
87
+
88
+ it 'adds membership triple to membership resource with #fragment' do
89
+ repo = RDF::Repository.new
90
+ subject = described_class.new(uri, repo)
91
+
92
+ mem_rs = subject.subject_uri / '#membership'
93
+
94
+ g = RDF::Graph.new << RDF::Statement(subject.subject_uri,
95
+ RDF::Vocab::LDP.membershipResource,
96
+ mem_rs)
97
+
98
+ subject.create(StringIO.new(g.dump(:ttl)), 'application/n-triples')
99
+ expect(subject.add(added).graph)
100
+ .to have_statement subject.make_membership_triple(added_uri)
101
+ end
102
+
103
+ it 'adds membership triple to container for LDP-NR membership resource' do
104
+ repo = RDF::Repository.new
105
+ container = described_class.new(uri, repo)
106
+
107
+ nr = RDF::LDP::NonRDFSource.new('http://example.org/moomin_file',
108
+ repo)
109
+
110
+ g = RDF::Graph.new << RDF::Statement(subject.subject_uri,
111
+ RDF::Vocab::LDP.membershipResource,
112
+ nr.to_uri)
113
+
114
+ container
115
+ .create(StringIO.new(g.dump(:ntriples)), 'application/n-triples')
116
+
117
+ nr.create(StringIO.new, 'application/n-triples')
118
+
119
+ container.add(added)
120
+ expect(container.graph)
121
+ .to have_statement container.make_membership_triple(added_uri)
122
+ end
123
+
124
+ context 'with multiple membership resources' do
125
+ it 'raises an error' do
126
+ subject.graph << RDF::Statement(subject.subject_uri,
127
+ RDF::Vocab::LDP.membershipResource,
128
+ subject.subject_uri)
129
+ subject.graph << RDF::Statement(subject.subject_uri,
130
+ RDF::Vocab::LDP.membershipResource,
131
+ (subject.subject_uri / 'moomin'))
132
+
133
+ expect { subject.add(added) }
134
+ .to raise_error RDF::LDP::RequestError
135
+ expect(subject.containment_triples).to be_empty
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#remove' do
142
+ let(:added) { described_class.new(added_uri, repo) }
143
+ let(:added_uri) { RDF::URI('http://ex.org/too-ticky') }
144
+
145
+ context 'when the membership resource exists' do
146
+ before { subject.create(StringIO.new, 'application/n-triples') }
147
+
148
+ it 'removes membership triple to membership resource' do
149
+ subject.graph << subject.make_membership_triple(added_uri)
150
+ expect(subject.remove(added_uri).graph)
151
+ .not_to have_statement subject.make_membership_triple(added_uri)
152
+ end
153
+ end
154
+ end
155
+
156
+ describe '#membership_constant_uri' do
157
+ it 'when created defaults to #subject_uri' do
158
+ subject.create(StringIO.new, 'application/n-triples')
159
+ expect(subject.membership_constant_uri).to eq subject.subject_uri
160
+ expect(subject.graph)
161
+ .to have_statement RDF::Statement(subject.subject_uri,
162
+ RDF::Vocab::LDP.membershipResource,
163
+ subject.subject_uri)
164
+ end
165
+
166
+ it 'gives membership resource' do
167
+ membership_resource = (subject.subject_uri / '#too-ticky')
168
+ subject.graph << RDF::Statement(subject.subject_uri,
169
+ RDF::Vocab::LDP.membershipResource,
170
+ membership_resource)
171
+ expect(subject.membership_constant_uri).to eq membership_resource
172
+ end
173
+
174
+ it 'raises an error if multiple are present' do
175
+ membership_resource = (subject.subject_uri / '#too-ticky')
176
+ subject.graph << RDF::Statement(subject.subject_uri,
177
+ RDF::Vocab::LDP.membershipResource,
178
+ membership_resource)
179
+
180
+ subject.graph << RDF::Statement(subject.subject_uri,
181
+ RDF::Vocab::LDP.membershipResource,
182
+ subject.subject_uri)
183
+
184
+ expect { subject.membership_constant_uri }
185
+ .to raise_error RDF::LDP::RequestError
186
+ end
187
+ end
188
+
189
+ describe '#membership_predicate' do
190
+ it 'when created returns a uri' do
191
+ subject.create(StringIO.new, 'application/n-triples')
192
+ expect(subject.membership_predicate).to be_a RDF::URI
193
+ end
194
+
195
+ it 'gives assigned member relation predicate for hasMember' do
196
+ subject.graph << has_member_statement
197
+
198
+ expect(subject.membership_predicate).to eq RDF::Vocab::DC.hasPart
199
+ end
200
+
201
+ it 'gives assigned member relation predicate for isMemberOf' do
202
+ subject.graph << is_member_of_statement
203
+
204
+ expect(subject.membership_predicate).to eq RDF::Vocab::DC.isPartOf
205
+ end
206
+
207
+ it 'raises an error if multiple relation predicates are present' do
208
+ subject.graph << has_member_statement
209
+ subject.graph << is_member_of_statement
210
+
211
+ expect { subject.membership_predicate }
212
+ .to raise_error RDF::LDP::RequestError
213
+ end
214
+ end
215
+
216
+ describe '#make_membership_triple' do
217
+ context 'with hasMember' do
218
+ before do
219
+ g = RDF::Graph.new << has_member_statement
220
+ subject.create(StringIO.new(g.dump(:ntriples)), 'application/n-triples')
221
+ end
222
+
223
+ it 'is constant - predicate - derived' do
224
+ expect(subject.make_membership_triple(uri))
225
+ .to eq RDF::Statement(subject.membership_constant_uri,
226
+ subject.membership_predicate,
227
+ uri)
228
+ end
229
+ end
230
+
231
+ context 'with isMemberOf' do
232
+ before do
233
+ g = RDF::Graph.new << is_member_of_statement
234
+ subject.create(StringIO.new(g.dump(:ntriples)), 'application/n-triples')
235
+ end
236
+
237
+ it 'is derived - predicate - constant' do
238
+ expect(subject.make_membership_triple(uri))
239
+ .to eq RDF::Statement(uri,
240
+ subject.membership_predicate,
241
+ subject.membership_constant_uri)
242
+ end
243
+ end
244
+ end
245
+ end