rdf-ldp 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -87,9 +87,11 @@ module RDF::LDP
|
|
87
87
|
predicate = inserted_content_relation
|
88
88
|
return resource.to_uri if predicate == MEMBER_SUBJECT_URI
|
89
89
|
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
if resource.non_rdf_source?
|
91
|
+
raise(NotAcceptable, "#{resource.to_uri} is an LDP-NR; cannot add " \
|
92
|
+
'it to an IndirectContainer with a content ' \
|
93
|
+
'relation.')
|
94
|
+
end
|
93
95
|
|
94
96
|
target = transaction || resource.graph
|
95
97
|
statements = target.query([resource.subject_uri, predicate, :o])
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module RDF
|
2
2
|
module LDP
|
3
|
+
##
|
4
|
+
# Provides an interaction model registry.
|
3
5
|
class InteractionModel
|
4
6
|
class << self
|
5
7
|
##
|
6
8
|
# Interaction models are in reverse order of preference for POST/PUT
|
7
|
-
# requests; e.g. if a client sends a request with Resource, RDFSource,
|
8
|
-
# BasicContainer headers, the server gives a basic container.
|
9
|
+
# requests; e.g. if a client sends a request with Resource, RDFSource,
|
10
|
+
# oand BasicContainer headers, the server gives a basic container.
|
9
11
|
#
|
10
|
-
# Interaction models are initialized in the correct order, but with no
|
11
|
-
# registered to handle them.
|
12
|
+
# Interaction models are initialized in the correct order, but with no
|
13
|
+
# class registered to handle them.
|
12
14
|
@@interaction_models = {
|
13
15
|
RDF::LDP::RDFSource.to_uri => nil,
|
14
16
|
RDF::LDP::Container.to_uri => nil,
|
@@ -17,43 +19,47 @@ module RDF
|
|
17
19
|
RDF::LDP::IndirectContainer.to_uri => nil,
|
18
20
|
RDF::LDP::NonRDFSource.to_uri => nil
|
19
21
|
}
|
20
|
-
|
22
|
+
|
21
23
|
##
|
22
|
-
# Register a new interaction model for one or more Link header URIs.
|
23
|
-
# will automatically be registered.
|
24
|
+
# Register a new interaction model for one or more Link header URIs.
|
25
|
+
# klass.to_uri will automatically be registered.
|
24
26
|
#
|
25
|
-
# @param [RDF::LDP::Resource] klass the implementation class to
|
27
|
+
# @param [RDF::LDP::Resource] klass the implementation class to
|
28
|
+
# register
|
26
29
|
# @param [Hash <Symbol, *>] opts registration options:
|
27
|
-
# :default [true, false] if true, klass will become the new default
|
28
|
-
# unrecognized Link headers
|
29
|
-
# :for [RDF::URI, Array<RDF::URI>] additional URIs for which klass
|
30
|
-
# the interaction model
|
30
|
+
# :default [true, false] if true, klass will become the new default
|
31
|
+
# klass for unrecognized Link headers
|
32
|
+
# :for [RDF::URI, Array<RDF::URI>] additional URIs for which klass
|
33
|
+
# should become the interaction model
|
31
34
|
#
|
32
35
|
# @return [RDF::LDP::Resource] klass
|
33
|
-
def register(klass, opts={})
|
36
|
+
def register(klass, opts = {})
|
34
37
|
unless klass.ancestors.include?(RDF::LDP::Resource)
|
35
|
-
raise ArgumentError,
|
38
|
+
raise ArgumentError,
|
39
|
+
'Interaction models must subclass `RDF::LDP::Resource`'
|
36
40
|
end
|
37
|
-
@@default = klass if opts[:default]
|
41
|
+
@@default = klass if opts[:default] || @@default.nil?
|
38
42
|
@@interaction_models[klass.to_uri] = klass
|
39
43
|
Array(opts[:for]).each do |model|
|
40
44
|
@@interaction_models[model] = klass
|
41
45
|
end
|
42
46
|
klass
|
43
47
|
end
|
44
|
-
|
48
|
+
|
45
49
|
##
|
46
|
-
# Find the appropriate interaction model given a set of Link header
|
50
|
+
# Find the appropriate interaction model given a set of Link header
|
51
|
+
# URIs.
|
47
52
|
#
|
48
53
|
# @param [Array<RDF::URI>] uris
|
49
54
|
#
|
50
|
-
# @return [Class] a subclass of {RDF::LDP::Resource} that most narrowly
|
51
|
-
# supplied `uris`, or the default interaction model if
|
55
|
+
# @return [Class] a subclass of {RDF::LDP::Resource} that most narrowly
|
56
|
+
# matches the supplied `uris`, or the default interaction model if
|
57
|
+
# nothing matches
|
52
58
|
def find(uris)
|
53
|
-
match = @@interaction_models.keys.reverse.find { |u| uris.include? u }
|
59
|
+
match = @@interaction_models.keys.reverse.find { |u| uris.include? u }
|
54
60
|
self.for(match) || @@default
|
55
61
|
end
|
56
|
-
|
62
|
+
|
57
63
|
##
|
58
64
|
# Find the interaction model registered for a given uri
|
59
65
|
#
|
@@ -63,23 +69,27 @@ module RDF
|
|
63
69
|
def for(uri)
|
64
70
|
@@interaction_models[uri]
|
65
71
|
end
|
66
|
-
|
72
|
+
|
67
73
|
##
|
68
74
|
# The default registered interaction model
|
69
75
|
def default
|
70
76
|
@@default
|
71
77
|
end
|
72
|
-
|
78
|
+
|
73
79
|
##
|
74
|
-
# Test an array of URIs to see if their interaction models are
|
75
|
-
# refer either to RDF models or
|
80
|
+
# Test an array of URIs to see if their interaction models are
|
81
|
+
# compatible (e.g., all of the URIs refer either to RDF models or
|
82
|
+
# non-RDF models, but not a combination of both).
|
76
83
|
#
|
77
84
|
# @param [Array<RDF::URI>] uris
|
78
|
-
# @return [TrueClass or FalseClass] true if the models specified by
|
85
|
+
# @return [TrueClass or FalseClass] true if the models specified by
|
86
|
+
# `uris` are compatible
|
79
87
|
def compatible?(uris)
|
80
|
-
classes
|
81
|
-
(rdf,non_rdf) =
|
82
|
-
|
88
|
+
classes = uris.collect { |m| self.for(m) }
|
89
|
+
(rdf, non_rdf) =
|
90
|
+
classes.compact.partition { |c| c.ancestors.include?(RDFSource) }
|
91
|
+
|
92
|
+
rdf.empty? || non_rdf.empty?
|
83
93
|
end
|
84
94
|
end
|
85
95
|
end
|
@@ -30,7 +30,9 @@ module RDF::LDP
|
|
30
30
|
# @param [storage_adapter] a class implementing the StorageAdapter interface
|
31
31
|
#
|
32
32
|
# @see RDF::LDP::Resource#initialize
|
33
|
-
def initialize(subject_uri,
|
33
|
+
def initialize(subject_uri,
|
34
|
+
data = RDF::Repository.new,
|
35
|
+
storage_adapter = DEFAULT_ADAPTER)
|
34
36
|
data ||= RDF::Repository.new # allows explict `nil` pass
|
35
37
|
@storage = storage_adapter.new(self)
|
36
38
|
super(subject_uri, data)
|
@@ -132,7 +134,7 @@ module RDF::LDP
|
|
132
134
|
#
|
133
135
|
# @raise [RDF::LDP::RequestError] when the request fails
|
134
136
|
def to_response
|
135
|
-
|
137
|
+
exists? && !destroyed? ? storage.io : []
|
136
138
|
end
|
137
139
|
|
138
140
|
private
|
data/lib/rdf/ldp/resource.rb
CHANGED
@@ -38,14 +38,15 @@ module RDF::LDP
|
|
38
38
|
# resource.exists? # => true
|
39
39
|
# resource.metagraph.dump :ttl
|
40
40
|
# # => "<http://example.org/moomin> a <http://www.w3.org/ns/ldp#Resource>;
|
41
|
-
#
|
41
|
+
# # <http://purl.org/dc/terms/modified>
|
42
|
+
# # "2015-10-25T14:24:56-07:00"^^xsd:dateTime ."
|
42
43
|
#
|
43
44
|
# @example updating a Resource updates the `#last_modified` date
|
44
45
|
# resource.last_modified
|
45
|
-
# # => #<DateTime: 2015-10-25T14:32:01-07:00
|
46
|
+
# # => #<DateTime: 2015-10-25T14:32:01-07:00...>
|
46
47
|
# resource.update('blah', 'text/plain')
|
47
48
|
# resource.last_modified
|
48
|
-
# # => #<DateTime: 2015-10-25T14:32:04-07:00
|
49
|
+
# # => #<DateTime: 2015-10-25T14:32:04-07:00...>
|
49
50
|
#
|
50
51
|
# @example destroying a Resource
|
51
52
|
# resource.exists? # => true
|
@@ -75,8 +76,8 @@ module RDF::LDP
|
|
75
76
|
# #<RDF::LDP::Resource:0x00564f4a646028
|
76
77
|
# @data=#<RDF::Repository:0x2b27a5391708()>,
|
77
78
|
# @exists=true,
|
78
|
-
# @metagraph=#<RDF::Graph:
|
79
|
-
# @subject_uri=#<RDF::URI:
|
79
|
+
# @metagraph=#<RDF::Graph:0xea7(http://example.org/moomin#meta)>,
|
80
|
+
# @subject_uri=#<RDF::URI:0xea8 URI:http://example.org/moomin>>]
|
80
81
|
#
|
81
82
|
# resource.request(:put, 200, {}, {}) # RDF::LDP::MethodNotAllowed: put
|
82
83
|
#
|
@@ -161,7 +162,7 @@ module RDF::LDP
|
|
161
162
|
.map { |link| RDF::URI.intern(link.href) }
|
162
163
|
|
163
164
|
return InteractionModel.default if models.empty?
|
164
|
-
|
165
|
+
|
165
166
|
raise NotAcceptable unless InteractionModel.compatible?(models)
|
166
167
|
|
167
168
|
InteractionModel.find(models)
|
@@ -302,9 +303,9 @@ module RDF::LDP
|
|
302
303
|
#
|
303
304
|
# @return [String] an HTTP Etag
|
304
305
|
#
|
305
|
-
# @note these etags are weak, but we allow clients to use them in
|
306
|
+
# @note these etags are weak, but we allow clients to use them in
|
306
307
|
# `If-Match` headers, and use weak comparison. This is in conflict with
|
307
|
-
# https://tools.ietf.org/html/rfc7232#section-3.1. See:
|
308
|
+
# https://tools.ietf.org/html/rfc7232#section-3.1. See:
|
308
309
|
# https://github.com/ruby-rdf/rdf-ldp/issues/68
|
309
310
|
#
|
310
311
|
# @see http://www.w3.org/TR/ldp#h-ldpr-gen-etags LDP ETag clause for GET
|
@@ -558,24 +559,25 @@ module RDF::LDP
|
|
558
559
|
##
|
559
560
|
# Sets the last modified date/time to now
|
560
561
|
#
|
561
|
-
# @param transaction [RDF::Transaction] the transaction scope in which to
|
562
|
+
# @param transaction [RDF::Transaction] the transaction scope in which to
|
562
563
|
# apply changes. If none (or `nil`) is given, the change is made outside
|
563
564
|
# any transaction scope.
|
564
565
|
def set_last_modified(transaction = nil)
|
565
|
-
|
566
|
-
# transactions do not support updates or pattern deletes, so we must
|
567
|
-
# ask the Repository for the current last_modified to delete the statement
|
568
|
-
# transactionally
|
566
|
+
return metagraph.update([subject_uri, MODIFIED_URI, DateTime.now]) unless
|
569
567
|
transaction
|
570
|
-
.delete RDF::Statement(subject_uri, MODIFIED_URI, last_modified,
|
571
|
-
graph_name: metagraph_name) if last_modified
|
572
568
|
|
569
|
+
# transactions do not support updates or pattern deletes, so we must
|
570
|
+
# ask the Repository for the current last_modified to delete the
|
571
|
+
# statement transactionally
|
572
|
+
if last_modified
|
573
573
|
transaction
|
574
|
-
.
|
574
|
+
.delete RDF::Statement(subject_uri, MODIFIED_URI, last_modified,
|
575
575
|
graph_name: metagraph_name)
|
576
|
-
else
|
577
|
-
metagraph.update([subject_uri, MODIFIED_URI, DateTime.now])
|
578
576
|
end
|
577
|
+
|
578
|
+
transaction
|
579
|
+
.insert RDF::Statement(subject_uri, MODIFIED_URI, DateTime.now,
|
580
|
+
graph_name: metagraph_name)
|
579
581
|
end
|
580
582
|
|
581
583
|
##
|
data/lib/rdf/ldp/spec.rb
ADDED
@@ -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
|