UNPKG

evaporate

Version:

Javascript library for browser to S3 multipart resumable uploads for browsers and with Node FileSystem (fs) Stream Support

360 lines (299 loc) 10.2 kB
import { DOMParser } from 'xmldom' import { jsdom } from 'jsdom' import { File } from 'file-api' import Evaporate from '../../evaporate' import sinon from 'sinon' import initResponse from '../fixtures/init-response' import completeResponse from '../fixtures/complete-response' import getPartsResponse from '../fixtures/get-parts-truncated-response' const CONTENT_TYPE_XML = { 'Content-Type': 'text/xml' } const CONTENT_TYPE_TEXT = { 'Content-Type': 'text/plain' } const AWS_UPLOAD_KEY = 'tests' global.document = jsdom('<body></body>') global.DOMParser = DOMParser File.prototype.slice = function (start = 0, end = null, contentType = '') { if (!end) { end = this.size } return new File({ size: end - start, path: this.path, name: this.name, type: this.type || contentType }) } global.File = File global.Blob = File let FileReaderMock = function () {} FileReaderMock.prototype.onloadend = function () {} FileReaderMock.prototype.readAsArrayBuffer = function () { this.onloadend(); } global.FileReader = FileReaderMock; global.AWS_BUCKET = 'bucket' global.AWS_UPLOAD_KEY = 'tests' const baseConfig = { signerUrl: 'http://what.ever/sign', aws_key: 'testkey', bucket: AWS_BUCKET, logging: false, maxRetryBackoffSecs: 0.1, abortCompletionThrottlingMs: 0 } function LocalStorage() { this.cache = {}; this.getItem = function (key) { return this.cache[key]; }; this.setItem = function (key, value) { return this.cache[key] = value; }; this.removeItem = function (key) { delete this.cache[key]; }; } global.localStorage = new LocalStorage(); global.testRequests = {} global.testContext = {} global.randomAwsKey = function () { return Math.random().toString().substr(2) + '_' + AWS_UPLOAD_KEY } let requestMap = { 'POST:uploads': 'initiate', 'POST:uploadId': 'complete', 'DELETE:uploadId': 'cancel', 'GET:uploadId': 'check for parts' } global.requestOrder = function (t) { var result = [] let r = testRequests[t.context.testId] r.forEach(function (r) { // Ignore the signing requests if (!r.url.match(/\/sign.*$/)) { var x = r.url.split('?'), y = x[1] ? x[1].split('&') : '', z = y[0] ? y[0].split('=')[0] : y if (z === 'partNumber') { z += '=' z += y[0].split('=')[1] } var v = z ? r.method + ':' + z : r.method result.push(requestMap[v] || v) } }) return result.join(',') } global.headersForMethod = function(t, method, urlRegex) { var r = urlRegex || /./ let requests = testRequests[t.context.testId] for (var i = 0; i < requests.length; i++) { var xhr = requests[i] if (xhr.method === method && xhr.url.match(r)) { return xhr.requestHeaders } } return {} } global.serverCommonCase = function (partRequestHandler) { let server = sinon.fakeServer.create({ respondImmediately: true }) server.respondWith('GET', /\/sign.*$/, (xhr) => { storeTestRequest(xhr) let payload payload = Array(29).join() if (xhr.url.match(/\/signv4.*$/)) { payload = '12345678901234567890123456v4' } else if (xhr.url.match(/\/signv2.*$/)) { payload = '1234567890123456789012345678' } xhr.respond(retryStatus(xhr, 'sign'), CONTENT_TYPE_TEXT, payload) }) server.respondWith('POST', /^.*\?uploads.*$/, (xhr) => { let context = storeTestRequest(xhr) context.authorization = xhr.requestHeaders.Authorization xhr.respond(retryStatus(xhr, 'init'), CONTENT_TYPE_XML, initResponse(AWS_BUCKET, AWS_UPLOAD_KEY)) }) server.respondWith('PUT', /^.*$/, (xhr) => { let context = storeTestRequest(xhr) if (typeof partRequestHandler === 'function') { if (typeof partRequestHandler(xhr, context) === 'undefined') { return; } } let status = retryStatus(xhr, 'part'), errResponse = ` <?xml version="1.0" encoding="UTF-8"?> <Error> <Code>NoSuchKey</Code> <Message>The resource you requested does not exist</Message> <Resource>/mybucket/myfoto.jpg</Resource> <RequestId>4442587FB7D0A2F9</RequestId> </Error>` xhr.respond(status, CONTENT_TYPE_XML, status === 200 ? '' : errResponse) }) server.respondWith('POST', /.*\?uploadId.*$/, (xhr) => { storeTestRequest(xhr) xhr.respond(retryStatus(xhr, 'complete'), CONTENT_TYPE_XML, completeResponse(AWS_BUCKET, AWS_UPLOAD_KEY)) }) server.respondWith('GET', /.*\?uploadId.*$/, (xhr) => { let context = storeTestRequest(xhr) let maxParts = context.maxGetParts || 0, marker = context.partNumberMarker || 0, status if (context.getPartsStatus === 404) { status = context.getPartsStatus } else { status = context.getPartsStatus || 200 } xhr.respond(status, CONTENT_TYPE_XML, getPartsResponse(AWS_BUCKET, AWS_UPLOAD_KEY, maxParts, marker++)) if (typeof context.partNumberMarker !== 'undefined') { context.partNumberMarker = marker; } }) server.respondWith('DELETE', /.*\?uploadId.*$/, (xhr) => { let context = storeTestRequest(xhr) if (context.deleteStatus === 404) { xhr.respond(context.deleteStatus) } else { xhr.respond(context.deleteStatus || 204) } }) server.respondWith('HEAD', /./, (xhr) => { let context = storeTestRequest(xhr) if (context.headStatus === 404) { xhr.respond(context.headStatus) } else { xhr.respond(context.headStatus, {eTag: context.headEtag || 'custom-eTag'}, '') } }) server.respondWith('GET', /\/time.*$/, (xhr) => { let match = xhr.url.match(/testId=(.+)\?/), payload if (match) { let testId = match[1], context = storeTestRequest(xhr, testId) if (typeof context.timeUrlCalled === 'undefined') { context.timeUrlCalled = 0 } context.timeUrlCalled += 1 payload = (context.timeUrlDate || new Date()).toISOString() } xhr.respond(retryStatus(xhr, 'time'), CONTENT_TYPE_TEXT, payload) }) function getContext(testId) { return testContext[testId] } function storeTestRequest(xhr, k) { k = k || xhr.requestHeaders.testId testRequests[k] = testRequests[k] || [] testRequests[k].push(xhr) return getContext(k) } function retryStatus(xhr, type, successStatus) { let context = getContext(xhr.requestHeaders.testId), status; if (!context) { return successStatus || 200 } if (context.retry(type)) { context.attempts += 1 if (context.attempts > context.maxRetries) { status = 200 context.attempts = 0 } else { status = context.errorStatus || 403 } } return status } } global.beforeEachSetup = function (t, file) { let testId = t.title if (testId in testContext) { console.error('Test case must be uniquely named:', testId) return } t.context.testId = testId t.context.requestedAwsObjectKey = randomAwsKey() t.context.requests = [] t.context.errMessages = [] t.context.attempts = 0 t.context.maxRetries = 1 t.context.retry = function (type) {} let addFile = file || new File({ path: '/tmp/file', size: 12000000, name: randomAwsKey() }) t.context.baseAddConfig = { name: t.context.requestedAwsObjectKey, file: addFile, maxRetryBackoffSecs: 0.1, abortCompletionThrottlingMs: 0 } t.context.cryptoMd5 = sinon.spy(function () { return 'md5Checksum' }) t.context.cryptoHexEncodedHash256 = sinon.spy(function () { return 'SHA256Value' }) testContext[testId] = t.context } global.newEvaporate = function (t, evapConfig) { evapConfig = evapConfig || {} t.context.evaporate = new Evaporate(Object.assign({}, baseConfig, {cryptoMd5Method: t.context.cryptoMd5}, evapConfig, { signHeaders: Object.assign({ testId: t.context.testId }, evapConfig.signHeaders) })) return t.context.evaporate; } global.evaporateAdd = function (t, evaporate, addConfig, configOverrides) { if (typeof addConfig.started === "function") { addConfig.user_started = addConfig.started; delete addConfig.started; } if (typeof addConfig.complete === "function") { addConfig.user_complete = addConfig.complete; delete addConfig.complete; } addConfig.xAmzHeadersAtInitiate = Object.assign({ testId: t.context.testId }, addConfig.xAmzHeadersAtInitiate) if (addConfig.xAmzHeadersAtUpload || addConfig.xAmzHeadersAtComplete) { addConfig.xAmzHeadersAtUpload = Object.assign({ testId: t.context.testId }, addConfig.xAmzHeadersAtUpload) addConfig.xAmzHeadersAtComplete = Object.assign({ testId: t.context.testId }, addConfig.xAmzHeadersAtComplete) if (addConfig.xAmzHeadersCommon) { addConfig.xAmzHeadersCommon = Object.assign({}, { testId: t.context.testId }, addConfig.xAmzHeadersCommon) } } else { addConfig.xAmzHeadersCommon = Object.assign({}, { testId: t.context.testId }, addConfig.xAmzHeadersCommon) } t.context.config = Object.assign({}, t.context.baseAddConfig, addConfig, { started: sinon.spy(function (id) { t.context.uploadId = id; if (typeof addConfig.user_started === "function") { addConfig.user_started(id); } }), complete: sinon.spy(function (xhr, awsKey) { t.context.completedAwsKey = awsKey; if (typeof addConfig.user_complete === "function") { addConfig.user_complete(xhr, awsKey); } }) }) return evaporate.add(t.context.config, configOverrides || {}) } global.testBase = function (t, addConfig, evapConfig) { addConfig = addConfig || {} evapConfig = evapConfig || {} t.context.evapConfig = Object.assign({}, baseConfig, {cryptoMd5Method: t.context.cryptoMd5}, evapConfig, { signHeaders: Object.assign({ testId: t.context.testId }, evapConfig.signHeaders) }) let configOverrides = addConfig.configOverrides; return Evaporate.create(t.context.evapConfig) .then(function (evaporate) { t.context.evaporate = evaporate return evaporateAdd(t, evaporate, addConfig, configOverrides) }) }