1 | const chai = require('chai'),
|
2 | chaiHttp = require('chai-http'),
|
3 | should = chai.should(),
|
4 | expect = chai.expect,
|
5 | BbPromise = require('bluebird'),
|
6 | fs = BbPromise.promisifyAll(require("fs-extra")),
|
7 | Azurite = require('./../lib/AzuriteBlob'),
|
8 | rp = require('request-promise'),
|
9 | path = require('path'),
|
10 | xml2js = require('xml2js');
|
11 |
|
12 | chai.use(chaiHttp);
|
13 |
|
14 | const containerName = 'testcontainer';
|
15 | const blockBlobName = 'testblockblob';
|
16 | const appendBlobName = 'testappendblob';
|
17 | const pageBlobName = 'testpageblob';
|
18 | const url = `http://localhost:10000`;
|
19 | const urlPath = `/devstoreaccount1`;
|
20 |
|
21 |
|
22 | function createBlob(containerNamex, blobNamex, payload, blobType) {
|
23 |
|
24 | const optionsContainer = {
|
25 | method: 'PUT',
|
26 | uri: `http://localhost:10000/devstoreaccount1/${containerNamex}?restype=container`,
|
27 | body: ''
|
28 | };
|
29 | const optionsBlob = {
|
30 | method: 'PUT',
|
31 | headers: {
|
32 | 'x-ms-blob-type': blobType,
|
33 | 'Content-Type': 'application/octet-stream'
|
34 | },
|
35 | uri: `http://localhost:10000/devstoreaccount1/${containerNamex}/${blobNamex}`,
|
36 | body: payload
|
37 | }
|
38 |
|
39 | return rp(optionsContainer)
|
40 | .then(() => {
|
41 | return rp(optionsBlob);
|
42 | });
|
43 | }
|
44 |
|
45 | describe('Blob HTTP API', () => {
|
46 | const azurite = new Azurite();
|
47 |
|
48 | before(() => {
|
49 | const location = path.join(process.env.AZURITE_LOCATION, 'BLOB');
|
50 | return azurite.init({ l: location, silent: 'true', overwrite: 'true' })
|
51 | .then(() => {
|
52 |
|
53 | const optionsContainer = {
|
54 | method: 'PUT',
|
55 | uri: `http://localhost:10000/devstoreaccount1/${containerName}?restype=container`,
|
56 | body: ''
|
57 | };
|
58 | const optionsBlockBlob = {
|
59 | method: 'PUT',
|
60 | headers: {
|
61 | 'x-ms-blob-type': 'BlockBlob',
|
62 | 'Content-Type': 'application/octet-stream'
|
63 | },
|
64 | uri: `http://localhost:10000/devstoreaccount1/${containerName}/${blockBlobName}`,
|
65 | body: 'abc123'
|
66 | }
|
67 | const optionsAppendBlob = {
|
68 | method: 'PUT',
|
69 | headers: {
|
70 | 'x-ms-blob-type': 'AppendBlob',
|
71 | 'Content-Type': 'application/octet-stream'
|
72 | },
|
73 | uri: `http://localhost:10000/devstoreaccount1/${containerName}/${appendBlobName}`,
|
74 | body: ''
|
75 | }
|
76 | const optionsPageBlob = {
|
77 | method: 'PUT',
|
78 | headers: {
|
79 | 'x-ms-blob-type': 'PageBlob',
|
80 | 'Content-Type': 'application/octet-stream'
|
81 | },
|
82 | uri: `http://localhost:10000/devstoreaccount1/${containerName}/${pageBlobName}`,
|
83 | body: ''
|
84 | }
|
85 | return rp(optionsContainer)
|
86 | .then(() => {
|
87 | return rp(optionsBlockBlob);
|
88 | })
|
89 | .then(() => {
|
90 | return rp(optionsAppendBlob);
|
91 | })
|
92 | .then(() => {
|
93 | return rp(optionsPageBlob);
|
94 | });
|
95 | });
|
96 | });
|
97 |
|
98 | after(() => {
|
99 | return azurite.close();
|
100 | });
|
101 |
|
102 | describe('PUT Block Blob', () => {
|
103 | it('should fail to create a block due to missing container', () => {
|
104 | return chai.request(url)
|
105 | .put(`${urlPath}/DOESNOTEXISTS/blob`)
|
106 | .set('x-ms-blob-type', 'BlockBlob')
|
107 | .set('Content-Type', 'application/octet-stream')
|
108 | .send('THIS IS CONTENT')
|
109 | .catch((e) => {
|
110 | e.should.have.status(404);
|
111 | })
|
112 | });
|
113 | it('should fail to create a block due to wrong or unsupported blob type', () => {
|
114 | return chai.request(url)
|
115 | .put(`${urlPath}/${containerName}/blob`)
|
116 | .set('x-ms-blob-type', 'NOTSUPPORTED')
|
117 | .set('Content-Type', 'application/octet-stream')
|
118 | .send('THIS IS CONTENT')
|
119 | .catch((e) => {
|
120 | e.should.have.status(400);
|
121 | });
|
122 | });
|
123 | it('should create a simple block blob without meta headers', () => {
|
124 | return chai.request(url)
|
125 | .put(`${urlPath}/${containerName}/blob`)
|
126 | .set('x-ms-blob-type', 'BlockBlob')
|
127 | .set('Content-Type', 'application/octet-stream')
|
128 | .send('abcdefghijklmn')
|
129 | .then((res) => {
|
130 | res.should.have.status(201);
|
131 | });
|
132 | });
|
133 | });
|
134 |
|
135 | describe('Put BlockList', () => {
|
136 | const putBlockListBlobName = 'dir/putBlockListBlobName';
|
137 | it('should create a block blob from a list of blocks', () => {
|
138 | const optionsBlockBlob = {
|
139 | method: 'PUT',
|
140 | headers: {
|
141 | 'Content-Type': 'application/octet-stream',
|
142 | 'Content-Length': 6
|
143 | },
|
144 | qs: {
|
145 | 'comp': 'block',
|
146 | 'blockid': 'AAAAAA=='
|
147 | },
|
148 | uri: `http://localhost:10000/devstoreaccount1/${containerName}/${putBlockListBlobName}`,
|
149 | body: 'AAAAAA'
|
150 | }
|
151 |
|
152 | return rp(optionsBlockBlob)
|
153 | .then(() => {
|
154 | optionsBlockBlob.body = 'BBBBBB';
|
155 | optionsBlockBlob.qs.blockid = 'BBBBBB=='
|
156 | return rp(optionsBlockBlob);
|
157 | })
|
158 | .then(() => {
|
159 | optionsBlockBlob.body = 'CCCCCC';
|
160 | optionsBlockBlob.qs.blockid = 'CCCCCC=='
|
161 | return rp(optionsBlockBlob);
|
162 | })
|
163 | .then(() => {
|
164 | optionsBlockBlob.body = 'DDDDDD';
|
165 | optionsBlockBlob.qs.blockid = 'DDDDDD=='
|
166 | return rp(optionsBlockBlob);
|
167 | })
|
168 | .then(() => {
|
169 | const xmlBody =
|
170 | `<!--?xml version="1.0" encoding="utf-8"?-->
|
171 | <BlockList>
|
172 | <Latest>AAAAAA==</Latest>
|
173 | <Latest>CCCCCC==</Latest>
|
174 | <Latest>AAAAAA==</Latest>
|
175 | </BlockList>`
|
176 | return chai.request(url)
|
177 | .put(`${urlPath}/${containerName}/${putBlockListBlobName}`)
|
178 | .query({ comp: 'blocklist' })
|
179 | .send(xmlBody)
|
180 | .then((res) => {
|
181 | res.should.have.status(201);
|
182 | });
|
183 | });
|
184 | });
|
185 | });
|
186 |
|
187 | describe('Delete Blob', () => {
|
188 | it('should delete an existing Block Blob', () => {
|
189 | return createBlob('deleteblobtest', 'blob', 'abc123', 'BlockBlob')
|
190 | .then(() => {
|
191 | return chai.request(url)
|
192 | .delete(`${urlPath}/deleteblobtest/blob`);
|
193 | })
|
194 | .then((res) => {
|
195 | res.should.have.status(202);
|
196 | })
|
197 | });
|
198 | it('should fail when deleting a non-existant blob', () => {
|
199 | return chai.request(url)
|
200 | .delete(`${urlPath}/deleteblobtest/DOESNOTEXIST`)
|
201 | .catch((e) => {
|
202 | e.should.have.status(404);
|
203 | });
|
204 | });
|
205 | it('should fail when deleting from a non-existant container', () => {
|
206 | return chai.request(url)
|
207 | .delete(`${urlPath}/DOESNOTEXIST/DOESNOTEXIST`)
|
208 | .catch((e) => {
|
209 | e.should.have.status(404);
|
210 | });
|
211 | });
|
212 | });
|
213 |
|
214 | describe('Append Blobs', () => {
|
215 | it('should create an append blob', () => {
|
216 | return chai.request(url)
|
217 | .put(`${urlPath}/${containerName}/appendBlob`)
|
218 | .set('x-ms-blob-type', 'AppendBlob')
|
219 | .set('Content-Type', 'application/octet-stream')
|
220 | .then((res) => {
|
221 | res.should.have.status(201);
|
222 | });
|
223 | });
|
224 | it('should append data to the append blob', () => {
|
225 | return chai.request(url)
|
226 | .put(`${urlPath}/${containerName}/appendBlob`)
|
227 | .query({ comp: 'appendblock' })
|
228 | .set('x-ms-blob-type', 'AppendBlob')
|
229 | .set('Content-Type', 'application/octet-stream')
|
230 | .send('abcdefghi')
|
231 | .then((res) => {
|
232 | res.should.have.status(201);
|
233 | });
|
234 | });
|
235 | it('should fail to create an append blob with size > 0', () => {
|
236 | return chai.request(url)
|
237 | .put(`${urlPath}/${containerName}/appendBlob`)
|
238 | .set('x-ms-blob-type', 'AppendBlob')
|
239 | .set('Content-Type', 'application/octet-stream')
|
240 | .send('abcdefg')
|
241 | .catch((e) => {
|
242 | e.should.have.status(409);
|
243 | });
|
244 | });
|
245 | });
|
246 |
|
247 | describe('Page Blobs', () => {
|
248 | it('should get an empty page list from the page blob', () => {
|
249 | return chai.request(url)
|
250 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
251 | .query({ comp: 'pagelist' })
|
252 | .then((res) => {
|
253 | res.should.have.status(200);
|
254 | xml2js.Parser().parseString(res.text, function(err, result) {
|
255 | expect(result.PageList).to.not.have.any.keys('PageRange');
|
256 | });
|
257 | });
|
258 | });
|
259 | it('should write data to the page blob range [0-511]', () => {
|
260 | const bodydata = Buffer.alloc(512)
|
261 | return chai.request(url)
|
262 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
263 | .query({ comp: 'page' })
|
264 | .set('x-ms-page-write', 'update')
|
265 | .set('x-ms-range', 'bytes=0-511')
|
266 | .set('Content-Type', 'application/octet-stream')
|
267 | .send(bodydata)
|
268 | .then((res) => {
|
269 | res.should.have.status(201);
|
270 | });
|
271 | });
|
272 | it('should fail to write data to the page blob with an invalid range', () => {
|
273 | const bodydata = Buffer.alloc(513)
|
274 | return chai.request(url)
|
275 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
276 | .query({ comp: 'page' })
|
277 | .set('x-ms-page-write', 'update')
|
278 | .set('x-ms-range', 'bytes=0-512')
|
279 | .set('Content-Type', 'application/octet-stream')
|
280 | .send(bodydata)
|
281 | .catch((e) => {
|
282 | e.should.have.status(416);
|
283 | });
|
284 | });
|
285 | it('should fail to write data to the page blob with an invalid body length', () => {
|
286 | const bodydata = Buffer.alloc(513)
|
287 | return chai.request(url)
|
288 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
289 | .query({ comp: 'page' })
|
290 | .set('x-ms-page-write', 'update')
|
291 | .set('x-ms-range', 'bytes=0-511')
|
292 | .set('Content-Type', 'application/octet-stream')
|
293 | .send(bodydata)
|
294 | .catch((e) => {
|
295 | e.should.have.status(400);
|
296 | });
|
297 | });
|
298 | it('should get the page range [0-511] from the page blob', () => {
|
299 | return chai.request(url)
|
300 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
301 | .query({ comp: 'pagelist' })
|
302 | .then((res) => {
|
303 | res.should.have.status(200);
|
304 | xml2js.Parser().parseString(res.text, function(err, result) {
|
305 | expect(result.PageList.PageRange.length).to.equal(1);
|
306 | expect(result.PageList.PageRange[0]).to.deep.equal({"Start":["0"],"End":["511"]});
|
307 | });
|
308 | });
|
309 | });
|
310 | it('should get the page range [0-511] from the page blob within range [0-1023]', () => {
|
311 | return chai.request(url)
|
312 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
313 | .query({ comp: 'pagelist' })
|
314 | .set('x-ms-range', 'bytes=0-1023')
|
315 | .then((res) => {
|
316 | res.should.have.status(200);
|
317 | xml2js.Parser().parseString(res.text, function(err, result) {
|
318 | expect(result.PageList.PageRange.length).to.equal(1);
|
319 | expect(result.PageList.PageRange[0]).to.deep.equal({"Start":["0"],"End":["511"]});
|
320 | });
|
321 | });
|
322 | });
|
323 | it('should fail to get the page list from the page blob within an invalid range', () => {
|
324 | return chai.request(url)
|
325 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
326 | .query({ comp: 'pagelist' })
|
327 | .set('x-ms-range', 'bytes=0-1095')
|
328 | .catch((e) => {
|
329 | e.should.have.status(416);
|
330 | });
|
331 | });
|
332 | it('should write data to the page blob range [1024-1535]', () => {
|
333 | const bodydata = Buffer.alloc(512)
|
334 | return chai.request(url)
|
335 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
336 | .query({ comp: 'page' })
|
337 | .set('x-ms-page-write', 'update')
|
338 | .set('x-ms-range', 'bytes=1024-1535')
|
339 | .set('Content-Type', 'application/octet-stream')
|
340 | .send(bodydata)
|
341 | .then((res) => {
|
342 | res.should.have.status(201);
|
343 | });
|
344 | });
|
345 | it('should get the page ranges [0-511],[1024-1535] from the page blob', () => {
|
346 | return chai.request(url)
|
347 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
348 | .query({ comp: 'pagelist' })
|
349 | .then((res) => {
|
350 | res.should.have.status(200);
|
351 | xml2js.Parser().parseString(res.text, function(err, result) {
|
352 | expect(result.PageList.PageRange.length).to.equal(2);
|
353 | expect(result.PageList.PageRange[0]).to.deep.equal({"Start":["0"],"End":["511"]});
|
354 | expect(result.PageList.PageRange[1]).to.deep.equal({"Start":["1024"],"End":["1535"]});
|
355 | });
|
356 | });
|
357 | });
|
358 | it('should write data to the page blob range [512-1023]', () => {
|
359 | const bodydata = Buffer.alloc(512)
|
360 | return chai.request(url)
|
361 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
362 | .query({ comp: 'page' })
|
363 | .set('x-ms-page-write', 'update')
|
364 | .set('x-ms-range', 'bytes=512-1023')
|
365 | .set('Content-Type', 'application/octet-stream')
|
366 | .send(bodydata)
|
367 | .then((res) => {
|
368 | res.should.have.status(201);
|
369 | });
|
370 | });
|
371 | it('should get the page range [0-1535] from the page blob', () => {
|
372 | return chai.request(url)
|
373 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
374 | .query({ comp: 'pagelist' })
|
375 | .then((res) => {
|
376 | res.should.have.status(200);
|
377 | xml2js.Parser().parseString(res.text, function(err, result) {
|
378 | expect(result.PageList.PageRange.length).to.equal(1);
|
379 | expect(result.PageList.PageRange[0]).to.deep.equal({"Start":["0"],"End":["1535"]});
|
380 | });
|
381 | });
|
382 | });
|
383 | it('should clear data in the page blob range [512-1023]', () => {
|
384 | return chai.request(url)
|
385 | .put(`${urlPath}/${containerName}/${pageBlobName}`)
|
386 | .query({ comp: 'page' })
|
387 | .set('x-ms-page-write', 'clear')
|
388 | .set('x-ms-range', 'bytes=512-1023')
|
389 | .then((res) => {
|
390 | res.should.have.status(201);
|
391 | });
|
392 | });
|
393 | it('should get the page ranges [0-511],[1024-1535] from the page blob', () => {
|
394 | return chai.request(url)
|
395 | .get(`${urlPath}/${containerName}/${pageBlobName}`)
|
396 | .query({ comp: 'pagelist' })
|
397 | .then((res) => {
|
398 | res.should.have.status(200);
|
399 | xml2js.Parser().parseString(res.text, function(err, result) {
|
400 | expect(result.PageList.PageRange.length).to.equal(2);
|
401 | expect(result.PageList.PageRange[0]).to.deep.equal({"Start":["0"],"End":["511"]});
|
402 | expect(result.PageList.PageRange[1]).to.deep.equal({"Start":["1024"],"End":["1535"]});
|
403 | });
|
404 | });
|
405 | });
|
406 | });
|
407 |
|
408 | describe('GET Blob', () => {
|
409 | it('should get the correct content of the Block Blob', () => {
|
410 | const optionsBlockBlobGet = {
|
411 | method: 'GET',
|
412 | headers: {
|
413 | 'Content-Type': 'application/octet-stream'
|
414 | },
|
415 | uri: `http://localhost:10000/devstoreaccount1/${containerName}/${blockBlobName}`
|
416 | }
|
417 | return rp(optionsBlockBlobGet)
|
418 | .then((res) => {
|
419 | expect(res).to.be.equal('abc123');
|
420 | });
|
421 | });
|
422 | it('should get the correct type of the append blob', () => {
|
423 | return chai.request(url)
|
424 | .get(`${urlPath}/${containerName}/${appendBlobName}`)
|
425 | .then((res) => {
|
426 | res.should.have.status(200);
|
427 | res.should.have.header('x-ms-blob-type', 'AppendBlob');
|
428 | });
|
429 | });
|
430 | });
|
431 |
|
432 | describe('Blob Metadata', () => {
|
433 | it('should update an existing blob with metadata.', () => {
|
434 | return chai.request(url)
|
435 | .put(`${urlPath}/${containerName}/${blockBlobName}`)
|
436 | .query({ comp: 'metadata' })
|
437 | .set('x-ms-meta-test1', 'value1')
|
438 | .set('x-ms-meta-test2', 'value2')
|
439 | .set('x-ms-meta-meta1', 'meta1Value')
|
440 | .then((res) => {
|
441 | res.should.have.status(200);
|
442 | });
|
443 | });
|
444 | it('should get the correct metadata', () => {
|
445 | return chai.request(url)
|
446 | .get(`${urlPath}/${containerName}/${blockBlobName}`)
|
447 | .query({ comp: 'metadata' })
|
448 | .then((res) => {
|
449 | res.should.have.status(200);
|
450 | res.should.have.header('x-ms-meta-test1', 'value1');
|
451 | res.should.have.header('x-ms-meta-test2', 'value2');
|
452 | res.should.have.header('x-ms-meta-meta1', 'meta1Value');
|
453 | res.should.have.header('Last-Modified');
|
454 | res.should.have.header('ETag');
|
455 | });
|
456 | });
|
457 | it('should fail to get metadata of a non-existant blob', () => {
|
458 | return chai.request(url)
|
459 | .get(`${urlPath}/${containerName}/BLOB_DOESNOTEXISTS`)
|
460 | .query({ comp: 'metadata' })
|
461 | .catch((e) => {
|
462 | e.should.have.status(404);
|
463 | });
|
464 | });
|
465 | it('should fail to get metadata of a blob in a non-existant container', () => {
|
466 | return chai.request(url)
|
467 | .get(`${urlPath}/CONTAINER_DOESNOTEXIST/BLOB_DOESNOTEXISTS`)
|
468 | .query({ comp: 'metadata' })
|
469 | .catch((e) => {
|
470 | e.should.have.status(404);
|
471 | });
|
472 | });
|
473 | });
|
474 |
|
475 | describe('Blob Properties', () => {
|
476 | it('should successfully set all system properties', () => {
|
477 | return chai.request(url)
|
478 | .put(`${urlPath}/${containerName}/${blockBlobName}`)
|
479 | .set('x-ms-blob-cache-control', 'true')
|
480 | .set('x-ms-blob-content-type', 'ContentType')
|
481 | .set('x-ms-blob-content-md5', 'ContentMD5')
|
482 | .set('x-ms-blob-content-encoding', 'ContentEncoding')
|
483 | .set('x-ms-blob-content-language', 'ContentLanguage')
|
484 | .query({ comp: 'properties' })
|
485 | .then((res) => {
|
486 | res.should.have.status(200);
|
487 | });
|
488 | });
|
489 | it('should get all previously set system properties', () => {
|
490 | return chai.request(url)
|
491 | .head(`${urlPath}/${containerName}/${blockBlobName}`)
|
492 | .then((res) => {
|
493 | res.should.have.status(200);
|
494 | res.should.have.header('ETag');
|
495 | res.should.have.header('Last-Modified');
|
496 | res.should.have.header('Content-Type', 'ContentType');
|
497 | res.should.have.header('Content-Encoding', 'ContentEncoding');
|
498 | res.should.have.header('Content-MD5', 'ContentMD5');
|
499 | res.should.have.header('Content-Language', 'ContentLanguage');
|
500 | res.should.have.header('Cache-Control', 'true');
|
501 | res.should.have.header('x-ms-blob-type', 'BlockBlob');
|
502 | });
|
503 | });
|
504 | });
|
505 | });
|