rdf-ldp 0.9.2 → 0.9.3
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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +34 -0
- data/VERSION +1 -1
- data/app/lamprey.rb +119 -40
- data/bin/lamprey +15 -1
- data/lib/rack/ldp.rb +18 -20
- data/lib/rdf/ldp.rb +3 -2
- data/lib/rdf/ldp/container.rb +15 -9
- data/lib/rdf/ldp/direct_container.rb +5 -3
- data/lib/rdf/ldp/indirect_container.rb +5 -3
- data/lib/rdf/ldp/interaction_model.rb +39 -29
- data/lib/rdf/ldp/non_rdf_source.rb +4 -2
- data/lib/rdf/ldp/resource.rb +20 -18
- data/lib/rdf/ldp/spec.rb +6 -0
- data/lib/rdf/ldp/spec/container.rb +337 -0
- data/lib/rdf/ldp/spec/direct_container.rb +245 -0
- data/lib/rdf/ldp/spec/indirect_container.rb +152 -0
- data/lib/rdf/ldp/spec/non_rdf_source.rb +138 -0
- data/lib/rdf/ldp/spec/rdf_source.rb +370 -0
- data/lib/rdf/ldp/spec/resource.rb +242 -0
- data/lib/rdf/ldp/storage_adapters/file_storage_adapter.rb +4 -4
- data/lib/rdf/ldp/version.rb +16 -3
- metadata +37 -2
@@ -0,0 +1,370 @@
|
|
1
|
+
require 'rdf/ldp/spec/resource'
|
2
|
+
|
3
|
+
shared_examples 'an RDFSource' do
|
4
|
+
it_behaves_like 'a Resource'
|
5
|
+
|
6
|
+
let(:uri) { RDF::URI('http://ex.org/moomin') }
|
7
|
+
subject { described_class.new('http://ex.org/moomin') }
|
8
|
+
it { is_expected.to be_rdf_source }
|
9
|
+
it { is_expected.not_to be_non_rdf_source }
|
10
|
+
|
11
|
+
describe '#parse_graph' do
|
12
|
+
it 'raises UnsupportedMediaType if no reader found' do
|
13
|
+
expect { subject.send(:parse_graph, StringIO.new('graph'), 'text/fake') }
|
14
|
+
.to raise_error RDF::LDP::UnsupportedMediaType
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'raises BadRequest if graph cannot be parsed' do
|
18
|
+
expect do
|
19
|
+
subject.send(:parse_graph,
|
20
|
+
StringIO.new('graph'),
|
21
|
+
'application/n-triples')
|
22
|
+
end.to raise_error RDF::LDP::BadRequest
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'parsing the graph' do
|
26
|
+
let(:graph) { RDF::Repository.new }
|
27
|
+
|
28
|
+
before do
|
29
|
+
graph << RDF::Statement(RDF::URI('http://ex.org/moomin'),
|
30
|
+
RDF.type,
|
31
|
+
RDF::Vocab::FOAF.Person,
|
32
|
+
graph_name: subject.subject_uri)
|
33
|
+
|
34
|
+
10.times do
|
35
|
+
graph << RDF::Statement(RDF::Node.new,
|
36
|
+
RDF::Vocab::DC.creator,
|
37
|
+
RDF::Node.new,
|
38
|
+
graph_name: subject.subject_uri)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'parses turtle' do
|
43
|
+
expect(subject.send(:parse_graph,
|
44
|
+
StringIO.new(graph.dump(:ttl)),
|
45
|
+
'text/turtle'))
|
46
|
+
.to be_isomorphic_with graph
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'parses ntriples' do
|
50
|
+
expect(subject.send(:parse_graph,
|
51
|
+
StringIO.new(graph.dump(:ntriples)),
|
52
|
+
'application/n-triples'))
|
53
|
+
.to be_isomorphic_with graph
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#etag' do
|
59
|
+
before do
|
60
|
+
subject.graph << statement
|
61
|
+
other.graph << statement
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:other) { described_class.new(RDF::URI('http://ex.org/blah')) }
|
65
|
+
|
66
|
+
let(:statement) do
|
67
|
+
RDF::Statement(RDF::URI('http://ex.org/m'),
|
68
|
+
RDF::Vocab::DC.title,
|
69
|
+
'moomin')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'is the same for equal graphs' do
|
73
|
+
expect(subject.etag).to eq other.etag
|
74
|
+
end
|
75
|
+
|
76
|
+
xit 'is different for different graphs' do
|
77
|
+
subject.graph <<
|
78
|
+
RDF::Statement(RDF::Node.new, RDF::Vocab::DC.title, 'mymble')
|
79
|
+
expect(subject.etag).not_to eq other.etag
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#create' do
|
84
|
+
let(:subject) { described_class.new(RDF::URI('http://ex.org/m')) }
|
85
|
+
let(:graph) { RDF::Graph.new }
|
86
|
+
|
87
|
+
it 'does not create when graph fails to parse' do
|
88
|
+
begin
|
89
|
+
subject.create(StringIO.new(graph.dump(:ttl)), 'text/moomin')
|
90
|
+
rescue; end
|
91
|
+
|
92
|
+
expect(subject).not_to exist
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns itself' do
|
96
|
+
expect(subject.create(StringIO.new(graph.dump(:ttl)), 'text/turtle'))
|
97
|
+
.to eq subject
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'yields a transaction' do
|
101
|
+
expect do |b|
|
102
|
+
subject.create(StringIO.new(graph.dump(:ttl)), 'text/turtle', &b)
|
103
|
+
end.to yield_with_args(be_kind_of(RDF::Transaction))
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'interprets NULL URI as this resource' do
|
107
|
+
graph << RDF::Statement(RDF::URI(), RDF::Vocab::DC.title, 'moomin')
|
108
|
+
|
109
|
+
created =
|
110
|
+
subject.create(StringIO.new(graph.dump(:ttl)), 'text/turtle').graph
|
111
|
+
|
112
|
+
expect(created)
|
113
|
+
.to have_statement RDF::Statement(subject.subject_uri,
|
114
|
+
RDF::Vocab::DC.title,
|
115
|
+
'moomin')
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'interprets Relative URIs as this based on this resource' do
|
119
|
+
graph << RDF::Statement(subject.subject_uri,
|
120
|
+
RDF::Vocab::DC.isPartOf,
|
121
|
+
RDF::URI('#moomin'))
|
122
|
+
|
123
|
+
created =
|
124
|
+
subject.create(StringIO.new(graph.dump(:ttl)), 'text/turtle').graph
|
125
|
+
|
126
|
+
expect(created)
|
127
|
+
.to have_statement RDF::Statement(subject.subject_uri,
|
128
|
+
RDF::Vocab::DC.isPartOf,
|
129
|
+
subject.subject_uri / '#moomin')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#update' do
|
134
|
+
let(:statement) do
|
135
|
+
RDF::Statement(subject.subject_uri, RDF::Vocab::DC.title, 'moomin')
|
136
|
+
end
|
137
|
+
|
138
|
+
let(:graph) { RDF::Graph.new << statement }
|
139
|
+
let(:content_type) { 'text/turtle' }
|
140
|
+
|
141
|
+
shared_examples 'updating rdf_sources' do
|
142
|
+
it 'changes the response' do
|
143
|
+
expect { subject.update(StringIO.new(graph.dump(:ttl)), content_type) }
|
144
|
+
.to change { subject.to_response }
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'changes etag' do
|
148
|
+
expect { subject.update(StringIO.new(graph.dump(:ttl)), content_type) }
|
149
|
+
.to change { subject.etag }
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'yields a transaction' do
|
153
|
+
expect do |b|
|
154
|
+
subject.update(StringIO.new(graph.dump(:ttl)), content_type, &b)
|
155
|
+
end.to yield_with_args(be_kind_of(RDF::Transaction))
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'with bad media type' do
|
159
|
+
it 'raises UnsupportedMediaType' do
|
160
|
+
graph_io = StringIO.new(graph.dump(:ttl))
|
161
|
+
|
162
|
+
expect { subject.update(graph_io, 'text/moomin') }
|
163
|
+
.to raise_error RDF::LDP::UnsupportedMediaType
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'does not update #last_modified' do
|
167
|
+
modified = subject.last_modified
|
168
|
+
begin
|
169
|
+
subject.update(StringIO.new(graph.dump(:ttl)), 'text/moomin')
|
170
|
+
rescue; end
|
171
|
+
|
172
|
+
expect(subject.last_modified).to eq modified
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
include_examples 'updating rdf_sources'
|
178
|
+
|
179
|
+
context 'when it exists' do
|
180
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
181
|
+
|
182
|
+
include_examples 'updating rdf_sources'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe '#patch' do
|
187
|
+
it 'raises UnsupportedMediaType when no media type is given' do
|
188
|
+
expect { subject.request(:patch, 200, {}, {}) }
|
189
|
+
.to raise_error RDF::LDP::UnsupportedMediaType
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'gives PreconditionFailed when trying to update with wrong Etag' do
|
193
|
+
env = { 'HTTP_IF_MATCH' => 'not an Etag' }
|
194
|
+
expect { subject.request(:PATCH, 200, { 'abc' => 'def' }, env) }
|
195
|
+
.to raise_error RDF::LDP::PreconditionFailed
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'ldpatch' do
|
199
|
+
it 'raises BadRequest when invalid document' do
|
200
|
+
env = { 'CONTENT_TYPE' => 'text/ldpatch',
|
201
|
+
'rack.input' => StringIO.new('---invalid---') }
|
202
|
+
expect { subject.request(:patch, 200, {}, env) }
|
203
|
+
.to raise_error RDF::LDP::BadRequest
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'handles patch' do
|
207
|
+
statement =
|
208
|
+
RDF::Statement(subject.subject_uri, RDF::Vocab::FOAF.name, 'Moomin')
|
209
|
+
patch = '@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .' \
|
210
|
+
"\n\nAdd { #{statement.subject.to_base} " \
|
211
|
+
"#{statement.predicate.to_base} #{statement.object.to_base} } ."
|
212
|
+
env = { 'CONTENT_TYPE' => 'text/ldpatch',
|
213
|
+
'rack.input' => StringIO.new(patch) }
|
214
|
+
|
215
|
+
expect { subject.request(:patch, 200, {}, env) }
|
216
|
+
.to change { subject.graph.statements.to_a }
|
217
|
+
.to(contain_exactly(statement))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
context 'sparql update' do
|
222
|
+
it 'raises BadRequest when invalid document' do
|
223
|
+
env = { 'CONTENT_TYPE' => 'application/sparql-update',
|
224
|
+
'rack.input' => StringIO.new('---invalid---') }
|
225
|
+
|
226
|
+
expect { subject.request(:patch, 200, {}, env) }
|
227
|
+
.to raise_error RDF::LDP::BadRequest
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'runs sparql update' do
|
231
|
+
update = "INSERT DATA { #{subject.subject_uri.to_base} "\
|
232
|
+
"#{RDF::Vocab::DC.title.to_base} 'moomin' . }"
|
233
|
+
|
234
|
+
env = { 'CONTENT_TYPE' => 'application/sparql-update',
|
235
|
+
'rack.input' => StringIO.new(update) }
|
236
|
+
|
237
|
+
expect { subject.request(:patch, 200, {}, env) }
|
238
|
+
.to change { subject.graph.count }.from(0).to(1)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe '#graph' do
|
244
|
+
it 'has a graph' do
|
245
|
+
expect(subject.graph).to be_a RDF::Enumerable
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe '#subject_uri' do
|
250
|
+
let(:uri) { RDF::URI('http://ex.org/moomin') }
|
251
|
+
|
252
|
+
it 'has a uri getter' do
|
253
|
+
expect(subject.subject_uri).to eq uri
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'aliases to #to_uri' do
|
257
|
+
expect(subject.to_uri).to eq uri
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe '#to_response' do
|
262
|
+
it 'gives the graph minus context' do
|
263
|
+
expect(subject.to_response.graph_name).to eq nil
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe '#request' do
|
268
|
+
context 'with :GET' do
|
269
|
+
it 'gives the subject' do
|
270
|
+
expect(subject.request(:GET, 200, { 'abc' => 'def' }, {}))
|
271
|
+
.to contain_exactly(200, a_hash_including('abc' => 'def'), subject)
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'does not call the graph' do
|
275
|
+
expect(subject).not_to receive(:graph)
|
276
|
+
subject.request(:GET, 200, { 'abc' => 'def' }, {})
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'returns 410 GONE when destroyed' do
|
280
|
+
allow(subject).to receive(:destroyed?).and_return true
|
281
|
+
expect { subject.request(:GET, 200, { 'abc' => 'def' }, {}) }
|
282
|
+
.to raise_error RDF::LDP::Gone
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context 'with :DELETE' do
|
287
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
288
|
+
|
289
|
+
it 'returns 204' do
|
290
|
+
expect(subject.request(:DELETE, 200, {}, {}).first).to eq 204
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'returns an empty body' do
|
294
|
+
expect(subject.request(:DELETE, 200, {}, {}).last)
|
295
|
+
.to be_empty
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'marks resource as destroyed' do
|
299
|
+
expect { subject.request(:DELETE, 200, {}, {}) }
|
300
|
+
.to change { subject.destroyed? }.from(false).to(true)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
context 'with :PUT',
|
305
|
+
if: described_class.private_method_defined?(:put) do
|
306
|
+
let(:graph) { RDF::Graph.new }
|
307
|
+
let(:env) do
|
308
|
+
{ 'rack.input' => StringIO.new(graph.dump(:ntriples)),
|
309
|
+
'CONTENT_TYPE' => 'application/n-triples' }
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'creates the resource' do
|
313
|
+
expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
|
314
|
+
.to change { subject.exists? }.from(false).to(true)
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'responds 201' do
|
318
|
+
expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env).first)
|
319
|
+
.to eq 201
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'returns the etag' do
|
323
|
+
expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env)[1]['ETag'])
|
324
|
+
.to eq subject.etag
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'when subject exists' do
|
328
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
329
|
+
|
330
|
+
it 'responds 200' do
|
331
|
+
expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env))
|
332
|
+
.to contain_exactly(200, a_hash_including('abc' => 'def'), subject)
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'replaces the graph with the input' do
|
336
|
+
graph <<
|
337
|
+
RDF::Statement(subject.subject_uri, RDF::Vocab::DC.title, 'moomin')
|
338
|
+
expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
|
339
|
+
.to change { subject.graph.statements.count }.to(1)
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'updates the etag' do
|
343
|
+
graph <<
|
344
|
+
RDF::Statement(subject.subject_uri, RDF::Vocab::DC.title, 'moomin')
|
345
|
+
expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
|
346
|
+
.to change { subject.etag }
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'returns the etag' do
|
350
|
+
expect(subject.request(:PUT, 200, { 'abc' => 'def' }, env)[1]['ETag'])
|
351
|
+
.to eq subject.etag
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'gives PreconditionFailed when trying to update with wrong Etag' do
|
355
|
+
env['HTTP_IF_MATCH'] = 'not an Etag'
|
356
|
+
expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
|
357
|
+
.to raise_error RDF::LDP::PreconditionFailed
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'succeeds when giving correct Etag' do
|
361
|
+
graph <<
|
362
|
+
RDF::Statement(subject.subject_uri, RDF::Vocab::DC.title, 'moomin')
|
363
|
+
env['HTTP_IF_MATCH'] = subject.etag
|
364
|
+
expect { subject.request(:PUT, 200, { 'abc' => 'def' }, env) }
|
365
|
+
.to change { subject.graph.statements.count }
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'rdf/spec'
|
3
|
+
require 'rdf/spec/matchers'
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
shared_examples 'a Resource' do
|
7
|
+
describe '.to_uri' do
|
8
|
+
it { expect(described_class.to_uri).to be_a RDF::URI }
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { described_class.new(uri) }
|
12
|
+
let(:uri) { RDF::URI 'http://example.org/moomin' }
|
13
|
+
|
14
|
+
it { is_expected.to be_ldp_resource }
|
15
|
+
it { is_expected.to respond_to :container? }
|
16
|
+
it { is_expected.to respond_to :rdf_source? }
|
17
|
+
it { is_expected.to respond_to :non_rdf_source? }
|
18
|
+
|
19
|
+
it { subject.send(:set_last_modified) }
|
20
|
+
|
21
|
+
describe '#exists?' do
|
22
|
+
it 'does not exist' do
|
23
|
+
expect(subject).not_to exist
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'while existing' do
|
27
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
28
|
+
|
29
|
+
subject { described_class.new(uri, repository) }
|
30
|
+
let(:repository) { RDF::Repository.new }
|
31
|
+
|
32
|
+
it 'exists' do
|
33
|
+
expect(subject).to exist
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'is different from same URI with trailing /' do
|
37
|
+
expect(described_class.new(uri + '/', repository)).not_to exist
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#allowed_methods' do
|
43
|
+
it 'responds to all methods returned' do
|
44
|
+
subject.allowed_methods.each do |method|
|
45
|
+
expect(subject.respond_to?(method.downcase, true)).to be true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'includes the MUST methods' do
|
50
|
+
expect(subject.allowed_methods).to include(:GET, :OPTIONS, :HEAD)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#create' do
|
55
|
+
it 'accepts two args' do
|
56
|
+
expect(described_class.instance_method(:create).arity).to eq 2
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'modified time' do
|
60
|
+
before { Timecop.freeze }
|
61
|
+
after { Timecop.return }
|
62
|
+
|
63
|
+
it 'sets last_modified' do
|
64
|
+
subject.create(StringIO.new, 'text/turtle')
|
65
|
+
expect(subject.last_modified).to eq DateTime.now
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'adds a type triple to metagraph' do
|
70
|
+
subject.create(StringIO.new, 'application/n-triples')
|
71
|
+
expect(subject.metagraph)
|
72
|
+
.to have_statement RDF::Statement(subject.subject_uri,
|
73
|
+
RDF.type,
|
74
|
+
described_class.to_uri)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'yields a transaction' do
|
78
|
+
expect { |b| subject.create(StringIO.new, 'application/n-triples', &b) }
|
79
|
+
.to yield_with_args(be_kind_of(RDF::Transaction))
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'marks resource as existing' do
|
83
|
+
expect { subject.create(StringIO.new, 'application/n-triples') }
|
84
|
+
.to change { subject.exists? }.from(false).to(true)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns self' do
|
88
|
+
expect(subject.create(StringIO.new, 'application/n-triples'))
|
89
|
+
.to eq subject
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'raises Conlict when already exists' do
|
93
|
+
subject.create(StringIO.new, 'application/n-triples')
|
94
|
+
expect { subject.create(StringIO.new, 'application/n-triples') }
|
95
|
+
.to raise_error RDF::LDP::Conflict
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#update' do
|
100
|
+
it 'accepts two args' do
|
101
|
+
expect(described_class.instance_method(:update).arity).to eq 2
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns self' do
|
105
|
+
expect(subject.update(StringIO.new, 'application/n-triples'))
|
106
|
+
.to eq subject
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'yields a changeset' do
|
110
|
+
expect { |b| subject.update(StringIO.new, 'application/n-triples', &b) }
|
111
|
+
.to yield_with_args(be_kind_of(RDF::Transaction))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#destroy' do
|
116
|
+
it 'accepts no args' do
|
117
|
+
expect(described_class.instance_method(:destroy).arity).to eq 0
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#metagraph' do
|
122
|
+
it 'returns a graph' do
|
123
|
+
expect(subject.metagraph).to be_a RDF::Graph
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'has the metagraph name for the resource' do
|
127
|
+
expect(subject.metagraph.graph_name).to eq subject.subject_uri / '#meta'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe '#etag' do
|
132
|
+
before { subject.create(StringIO.new, 'application/n-triples') }
|
133
|
+
|
134
|
+
it 'has an etag' do
|
135
|
+
expect(subject.etag).to be_a String
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'updates etag on change' do
|
139
|
+
expect { subject.update(StringIO.new, 'application/n-triples') }
|
140
|
+
.to change { subject.etag }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#last_modified' do
|
145
|
+
it 'returns nil when no dc:modified triple is present' do
|
146
|
+
expect(subject.last_modified).to be_nil
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'raises an error when exists without dc:modified triple is present' do
|
150
|
+
allow(subject).to receive(:exists?).and_return true
|
151
|
+
expect { subject.last_modified }.to raise_error RDF::LDP::RequestError
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'with dc:modified triple' do
|
155
|
+
before do
|
156
|
+
subject.metagraph.update([subject.subject_uri,
|
157
|
+
RDF::Vocab::DC.modified,
|
158
|
+
datetime])
|
159
|
+
end
|
160
|
+
|
161
|
+
let(:datetime) { DateTime.now }
|
162
|
+
|
163
|
+
it 'returns date in `dc:modified`' do
|
164
|
+
expect(subject.last_modified).to eq datetime
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#to_response' do
|
170
|
+
it 'returns an object that responds to #each' do
|
171
|
+
expect(subject.to_response).to respond_to :each
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '#request' do
|
176
|
+
it 'sends the message to itself' do
|
177
|
+
expect(subject).to receive(:blah)
|
178
|
+
subject.request(:BLAH, 200, {}, {})
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'raises MethodNotAllowed when method is unimplemented' do
|
182
|
+
allow(subject).to receive(:not_implemented)
|
183
|
+
.and_raise NotImplementedError
|
184
|
+
expect { subject.request(:not_implemented, 200, {}, {}) }
|
185
|
+
.to raise_error(RDF::LDP::MethodNotAllowed)
|
186
|
+
end
|
187
|
+
|
188
|
+
[:GET, :OPTIONS, :HEAD].each do |method|
|
189
|
+
it "responds to #{method}" do
|
190
|
+
expect(subject.request(method, 200, {}, {}).size).to eq 3
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
[:PATCH, :POST, :PUT, :DELETE, :TRACE, :CONNECT].each do |method|
|
195
|
+
it "responds to or errors on #{method}" do
|
196
|
+
g = RDF::Graph.new << [RDF::Node.new, RDF.type, 'moomin']
|
197
|
+
env = { 'CONTENT_TYPE' => 'application/n-triples',
|
198
|
+
'rack.input' => StringIO.new(g.dump(:ntriples)) }
|
199
|
+
|
200
|
+
begin
|
201
|
+
response = subject.request(method, 200, {}, env)
|
202
|
+
expect(response.size).to eq 3
|
203
|
+
rescue => e
|
204
|
+
expect(e).to be_a RDF::LDP::RequestError
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe 'HTTP headers' do
|
210
|
+
before { subject.create(StringIO.new, 'text/turtle') }
|
211
|
+
let(:headers) { subject.request(:GET, 200, {}, {})[1] }
|
212
|
+
|
213
|
+
it 'has ETag' do
|
214
|
+
expect(headers['ETag']).to eq subject.etag
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'has Last-Modified' do
|
218
|
+
expect(headers['Last-Modified']).to eq subject.last_modified.httpdate
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'has Allow' do
|
222
|
+
expect(headers['Allow']).to be_a String
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'has Link' do
|
226
|
+
expect(headers['Link']).to be_a String
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'responds to :GET' do
|
231
|
+
expect { subject.request(:GET, 200, {}, {}) }.not_to raise_error
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'responds to :HEAD' do
|
235
|
+
expect { subject.request(:OPTIONS, 200, {}, {}) }.not_to raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'responds to :OPTIONS' do
|
239
|
+
expect { subject.request(:OPTIONS, 200, {}, {}) }.not_to raise_error
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|