visor-image 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/visor +423 -0
- data/bin/visor-image +10 -0
- data/config/server.rb +14 -0
- data/lib/image/auth.rb +147 -0
- data/lib/image/cli.rb +397 -0
- data/lib/image/client.rb +490 -0
- data/lib/image/meta.rb +219 -0
- data/lib/image/routes/delete_all_images.rb +40 -0
- data/lib/image/routes/delete_image.rb +62 -0
- data/lib/image/routes/get_image.rb +78 -0
- data/lib/image/routes/get_images.rb +54 -0
- data/lib/image/routes/get_images_detail.rb +54 -0
- data/lib/image/routes/head_image.rb +51 -0
- data/lib/image/routes/post_image.rb +189 -0
- data/lib/image/routes/put_image.rb +205 -0
- data/lib/image/server.rb +307 -0
- data/lib/image/store/cumulus.rb +126 -0
- data/lib/image/store/file_system.rb +119 -0
- data/lib/image/store/hdfs.rb +149 -0
- data/lib/image/store/http.rb +78 -0
- data/lib/image/store/lunacloud.rb +126 -0
- data/lib/image/store/s3.rb +121 -0
- data/lib/image/store/store.rb +39 -0
- data/lib/image/store/walrus.rb +130 -0
- data/lib/image/version.rb +5 -0
- data/lib/visor-image.rb +30 -0
- data/spec/lib/client_spec.rb +0 -0
- data/spec/lib/meta_spec.rb +230 -0
- data/spec/lib/routes/delete_image_spec.rb +98 -0
- data/spec/lib/routes/get_image_spec.rb +78 -0
- data/spec/lib/routes/get_images_detail_spec.rb +104 -0
- data/spec/lib/routes/get_images_spec.rb +104 -0
- data/spec/lib/routes/head_image_spec.rb +51 -0
- data/spec/lib/routes/post_image_spec.rb +112 -0
- data/spec/lib/routes/put_image_spec.rb +109 -0
- data/spec/lib/server_spec.rb +62 -0
- data/spec/lib/store/cumulus_spec.rb +0 -0
- data/spec/lib/store/file_system_spec.rb +32 -0
- data/spec/lib/store/http_spec.rb +56 -0
- data/spec/lib/store/s3_spec.rb +37 -0
- data/spec/lib/store/store_spec.rb +36 -0
- data/spec/lib/store/walrus_spec.rb +0 -0
- metadata +217 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Visor
|
4
|
+
module Image
|
5
|
+
|
6
|
+
# Visor Image Store module encapsulates all store backend classes, plus a set of
|
7
|
+
# utility methods common to all stores.
|
8
|
+
#
|
9
|
+
module Store
|
10
|
+
extend self
|
11
|
+
include Visor::Common::Exception
|
12
|
+
|
13
|
+
# Base API mapping for the multiple storage backend classes
|
14
|
+
BACKENDS = {:s3 => Visor::Image::Store::S3,
|
15
|
+
:cumulus => Visor::Image::Store::Cumulus,
|
16
|
+
:walrus => Visor::Image::Store::Walrus,
|
17
|
+
:lunacloud => Visor::Image::Store::Lunacloud,
|
18
|
+
:hdfs => Visor::Image::Store::HDFS,
|
19
|
+
:file => Visor::Image::Store::FileSystem,
|
20
|
+
:http => Visor::Image::Store::HTTP}
|
21
|
+
|
22
|
+
# Get a store backend class object ready to use, based on a file URI or store name.
|
23
|
+
#
|
24
|
+
# @param string [String] The file location URI or backend name.
|
25
|
+
# @param config [Hash] A set of configurations for the wanted store, loaded from
|
26
|
+
# VISoR configuration file.
|
27
|
+
#
|
28
|
+
# @return [Object] An instantiated store object ready to use.
|
29
|
+
#
|
30
|
+
def get_backend(string, config)
|
31
|
+
name = URI(string).scheme || string
|
32
|
+
store = BACKENDS[name.to_sym]
|
33
|
+
raise UnsupportedStore, "The store '#{name}' is not supported" unless store
|
34
|
+
store.new(string, config)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 's3restful'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/em-http'
|
5
|
+
|
6
|
+
module Visor
|
7
|
+
module Image
|
8
|
+
module Store
|
9
|
+
|
10
|
+
# The Eucalyptus Walrus (Walrus) backend store.
|
11
|
+
#
|
12
|
+
# This class handles the management of image files located in the Walrus storage system,
|
13
|
+
# based on a URI like *walrus://<access_key>:<secret_key>@<host>:<port>/<bucket>/<image>*.
|
14
|
+
#
|
15
|
+
class Walrus
|
16
|
+
include Visor::Common::Exception
|
17
|
+
|
18
|
+
attr_accessor :uri, :config, :access_key, :secret_key, :bucket, :file, :host, :port
|
19
|
+
|
20
|
+
# Initializes a new Walrus store client object. Walrus credentials are loaded from the URI,
|
21
|
+
# on GET and DELETE operations, or from the configuration file for POST and PUT operation.
|
22
|
+
#
|
23
|
+
# @param [String] uri The URI of the file location.
|
24
|
+
# @param config [Hash] A set of configurations for the wanted store, loaded from
|
25
|
+
# VISoR configuration file.
|
26
|
+
#
|
27
|
+
# @return [Object] An instantiated Walrus store object ready to use.
|
28
|
+
#
|
29
|
+
def initialize(uri, config)
|
30
|
+
@uri = URI(uri)
|
31
|
+
@config = config[:walrus]
|
32
|
+
|
33
|
+
if @uri.scheme
|
34
|
+
@access_key = @uri.user
|
35
|
+
@secret_key = @uri.password
|
36
|
+
# Trick:
|
37
|
+
# To build a successful Walrus connection we need happening to sign the
|
38
|
+
# '/services/Walrus' in path too. If we append it to server's address it wont get
|
39
|
+
# signed in the authorization string, we need to prepend it to the bucket name
|
40
|
+
@bucket = File.join('services/Walrus', @uri.path.split('/')[3])
|
41
|
+
@file = @uri.path.split('/').last
|
42
|
+
@host = @uri.host
|
43
|
+
@port = @uri.port
|
44
|
+
else
|
45
|
+
@access_key = @config[:access_key]
|
46
|
+
@secret_key = @config[:secret_key]
|
47
|
+
@bucket = File.join('services/Walrus', @config[:bucket])
|
48
|
+
@host = @config[:host]
|
49
|
+
@port = @config[:port]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns a Happening library S3 Walrus compatible connection object.
|
54
|
+
#
|
55
|
+
# @return [Happening::S3::Item] A new Walrus connection object.
|
56
|
+
#
|
57
|
+
def connection
|
58
|
+
S3restful::S3::Item.new(bucket, file, server: host, port: port, protocol: 'http',
|
59
|
+
aws_access_key_id: access_key, aws_secret_access_key: secret_key)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the image file to clients, streamed in chunks.
|
63
|
+
#
|
64
|
+
# @return [Object] Yields the file, a chunk at time.
|
65
|
+
#
|
66
|
+
def get
|
67
|
+
s3 = connection.aget
|
68
|
+
finish = proc { yield nil }
|
69
|
+
|
70
|
+
s3.stream { |chunk| yield chunk }
|
71
|
+
s3.callback &finish
|
72
|
+
s3.errback &finish
|
73
|
+
end
|
74
|
+
|
75
|
+
# Saves the image file to the its final destination, based on the temporary file
|
76
|
+
# created by the server at data reception time.
|
77
|
+
#
|
78
|
+
# @param [String] id The image id.
|
79
|
+
# @param [File] tmp_file The temporary file descriptor.
|
80
|
+
# @param [String] format The image file format.
|
81
|
+
#
|
82
|
+
# @return [String, Integer] The generated file location URI and image file size.
|
83
|
+
#
|
84
|
+
# @raise [Duplicated] If the image file already exists.
|
85
|
+
#
|
86
|
+
def save(id, tmp_file, format)
|
87
|
+
@file = "#{id}.#{format}"
|
88
|
+
uri = "walrus://#{access_key}:#{secret_key}@#{host}:#{port}/#{bucket}/#{file}"
|
89
|
+
size = tmp_file.size
|
90
|
+
|
91
|
+
raise Duplicated, "The image file #{fp} already exists" if file_exists?(false)
|
92
|
+
STDERR.puts "COPYING!!"
|
93
|
+
|
94
|
+
connection.store tmp_file.path
|
95
|
+
|
96
|
+
[uri, size]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Deletes the image file from its location.
|
100
|
+
#
|
101
|
+
# @raise [NotFound] If the image file was not found.
|
102
|
+
#
|
103
|
+
def delete
|
104
|
+
connection.delete
|
105
|
+
end
|
106
|
+
|
107
|
+
# Check if the image file exists.
|
108
|
+
#
|
109
|
+
# @param [True, False] raise_exc (true) If it should raise exception or return
|
110
|
+
# true/false whether the file exists or not.
|
111
|
+
#
|
112
|
+
# @return [True, False] If raise_exc is false, return true/false whether the
|
113
|
+
# file exists or not.
|
114
|
+
#
|
115
|
+
# @raise [NotFound] If the image file was not found.
|
116
|
+
#
|
117
|
+
def file_exists?(raise_exc=true)
|
118
|
+
exist = nil
|
119
|
+
error = proc { exist = false }
|
120
|
+
success = proc { |res| exist = true if res.response_header.status == 200 }
|
121
|
+
|
122
|
+
connection.head(on_error: error, on_success: success)
|
123
|
+
raise NotFound, "No image file found at #{uri}" if raise_exc && !exist
|
124
|
+
exist
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/lib/visor-image.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'goliath'
|
2
|
+
require 'visor-common'
|
3
|
+
|
4
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
5
|
+
|
6
|
+
require 'image/version'
|
7
|
+
require 'image/meta'
|
8
|
+
require 'image/auth'
|
9
|
+
require 'image/cli'
|
10
|
+
require 'image/store/s3'
|
11
|
+
require 'image/store/cumulus'
|
12
|
+
require 'image/store/walrus'
|
13
|
+
require 'image/store/lunacloud'
|
14
|
+
require 'image/store/hdfs'
|
15
|
+
require 'image/store/http'
|
16
|
+
require 'image/store/file_system'
|
17
|
+
require 'image/store/store'
|
18
|
+
require 'image/routes/head_image'
|
19
|
+
require 'image/routes/get_images'
|
20
|
+
require 'image/routes/get_images_detail'
|
21
|
+
require 'image/routes/get_image'
|
22
|
+
require 'image/routes/post_image'
|
23
|
+
require 'image/routes/put_image'
|
24
|
+
require 'image/routes/delete_image'
|
25
|
+
require 'image/routes/delete_all_images'
|
26
|
+
require 'image/server'
|
27
|
+
require 'image/client'
|
28
|
+
|
29
|
+
|
30
|
+
|
File without changes
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Visor::Image::Meta do
|
4
|
+
|
5
|
+
let(:meta) { Visor::Image::Meta.new }
|
6
|
+
let(:not_found) { Visor::Common::Exception::NotFound }
|
7
|
+
let(:invalid) { Visor::Common::Exception::Invalid }
|
8
|
+
|
9
|
+
let(:valid_post) { {name: 'client_spec', architecture: 'i386', access: 'public'} }
|
10
|
+
let(:invalid_post) { {name: 'client_spec', architecture: 'i386', access: 'invalid'} }
|
11
|
+
|
12
|
+
let(:valid_update) { {architecture: 'x86_64'} }
|
13
|
+
let(:invalid_update) { {architecture: 'invalid'} }
|
14
|
+
|
15
|
+
inserted = []
|
16
|
+
|
17
|
+
before(:all) do
|
18
|
+
EM.synchrony do
|
19
|
+
inserted << meta.post_image(valid_post)[:_id]
|
20
|
+
inserted << meta.post_image(valid_post.merge(architecture: 'x86_64'))[:_id]
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:all) do
|
26
|
+
EM.synchrony do
|
27
|
+
inserted.each { |id| meta.delete_image(id) }
|
28
|
+
EM.stop
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#initialize" do
|
33
|
+
it "should create a new meta api object with default parameters" do
|
34
|
+
meta.host.should == Visor::Image::Meta::DEFAULT_HOST
|
35
|
+
meta.port.should == Visor::Image::Meta::DEFAULT_PORT
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should create a new meta api object with custom parameters" do
|
39
|
+
custom = Visor::Image::Meta.new(host: '0.0.0.0', port: 1111)
|
40
|
+
custom.host.should == '0.0.0.0'
|
41
|
+
custom.port.should == 1111
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#get_images" do
|
46
|
+
it "should return an array of hashes" do
|
47
|
+
EM.synchrony do
|
48
|
+
res = meta.get_images
|
49
|
+
res.should be_a Array
|
50
|
+
res.each { |h| h.should be_a Hash }
|
51
|
+
EM.stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should filter results by parameter" do
|
56
|
+
EM.synchrony do
|
57
|
+
res = meta.get_images(architecture: 'i386')
|
58
|
+
res.each { |h| h[:architecture].should == 'i386' }
|
59
|
+
EM.stop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should sort results by parameter and direction" do
|
64
|
+
EM.synchrony do
|
65
|
+
pub = meta.get_images(sort: 'architecture', dir: 'desc')
|
66
|
+
pub.first[:architecture].should == 'x86_64'
|
67
|
+
pub = meta.get_images(sort: 'architecture', dir: 'asc')
|
68
|
+
pub.first[:architecture].should == 'i386'
|
69
|
+
EM.stop
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should raise if no matches found" do
|
74
|
+
EM.synchrony do
|
75
|
+
lambda { meta.get_images(:name => 'fake') }.should raise_error not_found
|
76
|
+
EM.stop
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#get_images_detail" do
|
82
|
+
it "should return an array of hashes" do
|
83
|
+
EM.synchrony do
|
84
|
+
res = meta.get_images
|
85
|
+
res.should be_a Array
|
86
|
+
res.each { |h| h.should be_a Hash }
|
87
|
+
EM.stop
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should filter results by parameter" do
|
92
|
+
EM.synchrony do
|
93
|
+
res = meta.get_images(architecture: 'x86_64')
|
94
|
+
res.each { |h| h[:architecture].should == 'x86_64' }
|
95
|
+
EM.stop
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should sort results by parameter and direction" do
|
100
|
+
EM.synchrony do
|
101
|
+
pub = meta.get_images(sort: 'architecture', dir: 'desc')
|
102
|
+
pub.first[:architecture].should == 'x86_64'
|
103
|
+
pub = meta.get_images(sort: 'architecture', dir: 'asc')
|
104
|
+
pub.first[:architecture].should == 'i386'
|
105
|
+
EM.stop
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should raise if no matches found" do
|
110
|
+
EM.synchrony do
|
111
|
+
lambda { meta.get_images(:name => 'fake') }.should raise_error not_found
|
112
|
+
EM.stop
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#get_image" do
|
118
|
+
before(:each) do
|
119
|
+
EM.synchrony do
|
120
|
+
@id = meta.post_image(valid_post)[:_id]
|
121
|
+
inserted << @id
|
122
|
+
@image = meta.get_image(@id)
|
123
|
+
EM.stop
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should return a hash" do
|
128
|
+
@image.should be_a Hash
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should return the asked image metadata" do
|
132
|
+
@image[:_id].should == @id
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should raise an exception if image not found" do
|
136
|
+
fake_id = 0
|
137
|
+
EM.synchrony do
|
138
|
+
lambda { meta.get_image(fake_id) }.should raise_error not_found
|
139
|
+
EM.stop
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe "#delete_image" do
|
145
|
+
before(:each) do
|
146
|
+
EM.synchrony do
|
147
|
+
@id = meta.post_image(valid_post)[:_id]
|
148
|
+
@image = meta.delete_image(@id)
|
149
|
+
EM.stop
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return a hash" do
|
154
|
+
@image.should be_a Hash
|
155
|
+
end
|
156
|
+
|
157
|
+
it "should return the deleted image metadata" do
|
158
|
+
@image[:_id].should == @id
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should trully delete that image from database" do
|
162
|
+
EM.synchrony do
|
163
|
+
lambda { meta.get_image(@id) }.should raise_error not_found
|
164
|
+
EM.stop
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should raise an exception if image not found" do
|
169
|
+
fake_id = 0
|
170
|
+
EM.synchrony do
|
171
|
+
lambda { meta.delete_image(fake_id) }.should raise_error not_found
|
172
|
+
EM.stop
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "#post_image" do
|
178
|
+
before(:each) do
|
179
|
+
EM.synchrony do
|
180
|
+
@image = meta.post_image(valid_post)
|
181
|
+
inserted << @image[:_id]
|
182
|
+
EM.stop
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should return a hash" do
|
187
|
+
@image.should be_a Hash
|
188
|
+
end
|
189
|
+
|
190
|
+
it "should return posted image metadata" do
|
191
|
+
@image[:_id].should be_a(String)
|
192
|
+
@image[:access].should == valid_post[:access]
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should raise an exception if meta validation fails" do
|
196
|
+
EM.synchrony do
|
197
|
+
lambda { meta.post_image(invalid_post) }.should raise_error invalid
|
198
|
+
EM.stop
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "#put_image" do
|
204
|
+
before :each do
|
205
|
+
EM.synchrony do
|
206
|
+
@id = meta.post_image(valid_post)[:_id]
|
207
|
+
inserted << @id
|
208
|
+
@image = meta.put_image(@id, valid_update)
|
209
|
+
EM.stop
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should return a hash" do
|
214
|
+
@image.should be_a Hash
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should return update image metadata" do
|
218
|
+
@image[:_id].should == @id
|
219
|
+
@image[:architecture].should == valid_update[:architecture]
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should raise an exception if meta validation fails" do
|
223
|
+
EM.synchrony do
|
224
|
+
lambda { meta.put_image(@id, invalid_update) }.should raise_error invalid
|
225
|
+
EM.stop
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Visor::Image::Server do
|
4
|
+
|
5
|
+
let(:test_api) { Visor::Image::Server }
|
6
|
+
let(:err) { Proc.new { fail "API request failed" } }
|
7
|
+
|
8
|
+
let(:accept_json) { {'Accept' => 'application/json'} }
|
9
|
+
let(:accept_xml) { {'Accept' => 'application/xml'} }
|
10
|
+
|
11
|
+
let(:parse_opts) { {symbolize_names: true} }
|
12
|
+
let(:valid_post) { {name: 'server_spec', architecture: 'i386', access: 'public'} }
|
13
|
+
let(:api_options) { {config: File.expand_path(File.join(File.dirname(__FILE__), '../../../', 'config/server.rb'))} }
|
14
|
+
|
15
|
+
inserted = []
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
EM.synchrony do
|
19
|
+
inserted << DB.post_image(valid_post)[:_id]
|
20
|
+
EM.stop
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
after(:all) do
|
25
|
+
EM.synchrony do
|
26
|
+
inserted.each { |id| DB.delete_image(id) }
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# DELETE /images/<id>
|
33
|
+
#
|
34
|
+
describe "DELETE /images/:id" do
|
35
|
+
|
36
|
+
it "should delete an image metadata" do
|
37
|
+
id = inserted.delete_at(0)
|
38
|
+
|
39
|
+
with_api(test_api, api_options) do
|
40
|
+
delete_request({:path => "/images/#{id}"}, err) do |c|
|
41
|
+
assert_200 c
|
42
|
+
c.response.should be_a String
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
with_api(test_api, api_options) do
|
47
|
+
delete_request({:path => "/images/#{id}"}, err) do |c|
|
48
|
+
assert_404 c
|
49
|
+
c.response.should match /id/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return a JSON string if no accept header provided" do
|
55
|
+
with_api(test_api, api_options) do
|
56
|
+
id = inserted.delete_at(0)
|
57
|
+
delete_request({:path => "/images/#{id}"}, err) do |c|
|
58
|
+
assert_200 c
|
59
|
+
c.response.should match /\{/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return a JSON string if accepted" do
|
65
|
+
with_api(test_api, api_options) do
|
66
|
+
id = inserted.delete_at(0)
|
67
|
+
delete_request({:path => "/images/#{id}", head: accept_json}, err) do |c|
|
68
|
+
assert_200 c
|
69
|
+
c.response.should match /\{/
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return a XML document if accepted" do
|
75
|
+
with_api(test_api, api_options) do
|
76
|
+
id = inserted.delete_at(0)
|
77
|
+
delete_request({:path => "/images/#{id}", head: accept_xml}, err) do |c|
|
78
|
+
assert_200 c
|
79
|
+
c.response.should match /\<?xml/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should raise a HTTPNotFound 404 error if no images found" do
|
85
|
+
with_api(test_api, api_options) do
|
86
|
+
delete_request({:path => "/images/fakeid"}, err) do |c|
|
87
|
+
assert_404 c
|
88
|
+
c.response.should match /fakeid/
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should raise a HTTPInternalServer 500 error if no server error" do
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|