rdf-ldp 0.9.2 → 2.1.0

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.
@@ -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