SimpleRestClients/test/SimpleWebRequest.spec.ts

440 строки
17 KiB
TypeScript

import * as faker from 'faker';
import {
ErrorHandlingType,
SimpleWebRequest,
SimpleWebRequestOptions,
WebErrorResponse,
WebRequestPriority,
} from '../src/SimpleWebRequest';
import { asyncTick, DETAILED_RESPONSE } from './helpers';
describe('SimpleWebRequest', () => {
beforeEach(() => {
jasmine.Ajax.install();
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
it('performs GET request', () => {
const requestOptions = { contentType: 'json' };
const requestHeaders = { 'Accept': 'application/json' };
const statusCode = 200;
const onSuccess = jasmine.createSpy('onSuccess');
const method = 'GET';
const url = faker.internet.url();
const responseParsingException = undefined;
const response = {
...DETAILED_RESPONSE,
requestOptions: { ...requestOptions, priority: WebRequestPriority.Normal },
requestHeaders,
method,
url,
responseParsingException,
};
let request: any;
setTimeout(() => {
request = jasmine.Ajax.requests.mostRecent();
request.respondWith({ responseText: JSON.stringify(''), status: statusCode });
}, 0);
return new SimpleWebRequest<string>(method, url, requestOptions)
.start()
.then(onSuccess)
.then(() => {
expect(request.url).toEqual(url);
expect(request.method).toEqual(method);
expect(request.status).toEqual(statusCode);
expect(onSuccess).toHaveBeenCalledWith(response);
});
});
it('sends json POST request', () => {
const sendData = {
title: faker.name.title(),
text: faker.lorem.text(),
};
const requestOptions = { sendData };
const statusCode = 201;
const onSuccess = jasmine.createSpy('onSuccess');
const method = 'POST';
const body = { ...sendData, id: faker.random.uuid() };
const url = faker.internet.url();
const responseParsingException = undefined;
const response = {
...DETAILED_RESPONSE,
requestOptions: { ...requestOptions, priority: WebRequestPriority.Normal },
statusCode,
method,
body,
url,
responseParsingException,
};
let request: any;
setTimeout(() => {
request = jasmine.Ajax.requests.mostRecent();
request.respondWith({ responseText: JSON.stringify(body), status: statusCode });
}, 0);
return new SimpleWebRequest<string>(method, url, requestOptions)
.start()
.then(onSuccess)
.then(() => {
expect(request.url).toEqual(url);
expect(request.method).toEqual(method);
expect(request.status).toEqual(statusCode);
expect(onSuccess).toHaveBeenCalledWith(response);
});
});
it('allows to set request headers', () => {
const headers = {
'X-Requested-With': 'XMLHttpRequest',
'Max-Forwards': '10',
};
const method = 'POST';
const url = faker.internet.url();
let request: any;
setTimeout(() => {
request = jasmine.Ajax.requests.mostRecent();
request.respondWith({ status: 200 });
}, 0);
return new SimpleWebRequest<string>(method, url, {}, () => headers).start().then(() => {
expect(request.requestHeaders['X-Requested-With']).toEqual(headers['X-Requested-With']);
expect(request.requestHeaders['Max-Forwards']).toEqual(headers['Max-Forwards']);
});
});
it('forbids to set Accept header', () => {
spyOn(console, 'error');
const headers = {
'Accept': 'application/xml',
};
const method = 'GET';
const url = faker.internet.url();
const error = `Don't set Accept with options.headers -- use it with the options.acceptType property`;
const request = new SimpleWebRequest<string>(method, url, {}, () => headers);
expect(() => (request as any)._fire()).toThrowError(error);
expect(console.error).toHaveBeenCalledWith(error);
});
it('forbids to set Content-Type header', () => {
spyOn(console, 'error');
const headers = {
'Content-Type': 'application/xml',
};
const method = 'GET';
const url = faker.internet.url();
const error = `Don't set Content-Type with options.headers -- use it with the options.contentType property`;
const request = new SimpleWebRequest<string>(method, url, {}, () => headers);
expect(() => (request as any)._fire()).toThrowError(error);
expect(console.error).toHaveBeenCalledWith(error);
});
describe('blocking', () => {
let maxRequests = 0;
beforeEach(() => {
maxRequests = SimpleWebRequestOptions.MaxSimultaneousRequests;
SimpleWebRequestOptions.MaxSimultaneousRequests = 0;
jasmine.clock().install();
});
afterEach(() => {
SimpleWebRequestOptions.MaxSimultaneousRequests = maxRequests;
jasmine.clock().uninstall();
});
it('executes the requests by priority and age', () => {
const url = faker.internet.url();
const method = 'GET';
const onSuccessLow1 = jasmine.createSpy('onSuccessLow1');
const onSuccessCritical1 = jasmine.createSpy('onSuccessCritical1');
const onSuccessLow2 = jasmine.createSpy('onSuccessLow2');
const onSuccessCritical2 = jasmine.createSpy('onSuccessCritical2');
const status = 200;
const p1 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Low })
.start().then(onSuccessLow1);
jasmine.clock().tick(10);
const p2 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical })
.start().then(onSuccessCritical1);
jasmine.clock().tick(10);
const p3 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Low })
.start().then(onSuccessLow2);
jasmine.clock().tick(10);
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
// add a new request to kick the queue
const p4 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical })
.start().then(onSuccessCritical2);
asyncTick().then(() => {
// only one is executed
expect(jasmine.Ajax.requests.count()).toBe(1);
jasmine.Ajax.requests.mostRecent().respondWith({status});
});
return p2.then(() => {
// they're executed in correct order
expect(onSuccessCritical1).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(2);
jasmine.Ajax.requests.mostRecent().respondWith({status});
return p4;
}).then(() => {
expect(onSuccessCritical2).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(3);
jasmine.Ajax.requests.mostRecent().respondWith({status});
return p1;
}).then(() => {
expect(onSuccessLow1).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(4);
jasmine.Ajax.requests.mostRecent().respondWith({status});
return p3;
}).then(() => {
expect(onSuccessLow2).toHaveBeenCalled();
});
});
it('blocks the request with custom promise', () => {
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
const url = faker.internet.url();
const method = 'GET';
let blockResolver: () => void = () => undefined;
const blockPromise = new Promise<void>((res, rej) => { blockResolver = res; });
const onSuccess1 = jasmine.createSpy('onSuccess1');
const p1 = new SimpleWebRequest<string>(method, url, {}, undefined, () => blockPromise).start().then(onSuccess1);
asyncTick().then(() => {
expect(jasmine.Ajax.requests.count()).toBe(0);
blockResolver();
return asyncTick();
}).then(() => {
const request = jasmine.Ajax.requests.mostRecent();
request.respondWith({ status: 200 });
});
return p1.then(() => {
expect(onSuccess1).toHaveBeenCalled();
});
});
it('after the request is unblocked, it\'s returned to the queue with correct priority', () => {
const url = faker.internet.url();
const method = 'GET';
let blockResolver: () => void = () => undefined;
const blockPromise = new Promise<void>((res, rej) => { blockResolver = res; });
const onSuccessHigh = jasmine.createSpy('onSuccessHigh');
const onSuccessLow = jasmine.createSpy('onSuccessLow');
const onSuccessCritical = jasmine.createSpy('onSuccessCritical');
const p1 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.High }, undefined, () => blockPromise)
.start()
.then(onSuccessHigh);
jasmine.clock().tick(10);
const p2 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Low }).start().then(onSuccessLow);
jasmine.clock().tick(10);
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
// add a new request to kick the queue
const p3 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical }).start().then(onSuccessCritical);
// unblock the request
blockResolver();
// have to do an awkward async tick to get the blocking blocker to resolve before the request goes out
asyncTick().then(() => {
expect(jasmine.Ajax.requests.count()).toBe(1);
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
});
return p3.then(() => {
// first the critical one gets sent
expect(onSuccessCritical).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(2);
// then the high, which was returned to the queue at after getting unblocked
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
return p1;
}).then(() => {
expect(onSuccessHigh).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(3);
// and the low priority one gets sent last
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
return p2;
}).then(() => {
expect(onSuccessLow).toHaveBeenCalled();
});
});
it('checks the blocked function again, once the request is on top of the queue', () => {
const url = faker.internet.url();
const method = 'GET';
let blockResolver: () => void = () => undefined;
const blockPromise = new Promise<void>((res, rej) => { blockResolver = res; });
const onSuccessCritical = jasmine.createSpy('onSuccessCritical');
const onSuccessHigh = jasmine.createSpy('onSuccessHigh');
const onSuccessHigh2 = jasmine.createSpy('onSuccessHigh2');
const blockSpy = jasmine.createSpy('blockSpy').and.callFake(() => blockPromise);
const p1 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical }, undefined, blockSpy)
.start()
.then(onSuccessCritical);
jasmine.clock().tick(10);
const p2 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.High }).start().then(onSuccessHigh);
jasmine.clock().tick(10);
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
// add a new request to kick the queue
const p3 = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.High }).start().then(onSuccessHigh2);
asyncTick().then(() => {
expect(blockSpy).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(1);
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
});
return p2.then(() => {
expect(onSuccessHigh).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(2);
// unblock the request, it will go back to the queue after the currently executed request
blockResolver();
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
return p3;
}).then(() => {
expect(onSuccessHigh2).toHaveBeenCalled();
expect(jasmine.Ajax.requests.count()).toBe(3);
jasmine.Ajax.requests.mostRecent().respondWith({ status: 200 });
return p1;
}).then(() => {
expect(onSuccessCritical).toHaveBeenCalled();
});
});
it('fails the request, if the blocking promise rejects', done => {
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
const url = faker.internet.url();
const method = 'GET';
let blockRejecter: (err: any) => void = () => undefined;
const blockPromise = new Promise<void>((res, rej) => { blockRejecter = rej; });
const errorString = 'Terrible error';
new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical }, undefined, () => blockPromise)
.start()
.then(() => fail(), (err: WebErrorResponse) => {
expect(err.statusCode).toBe(0);
expect(err.statusText).toBe('_blockRequestUntil rejected: ' + errorString);
done();
});
blockRejecter(errorString);
});
it('does not attempt to fire aborted request, if it was aborted while blocked', () => {
SimpleWebRequestOptions.MaxSimultaneousRequests = 1;
const url = faker.internet.url();
const method = 'GET';
let blockResolver: () => void = () => undefined;
const blockPromise = new Promise<void>((res, rej) => { blockResolver = res; });
const req = new SimpleWebRequest<string>(method, url, { priority: WebRequestPriority.Critical }, undefined, () => blockPromise);
const p1 = req.start();
return asyncTick().then(() => {
expect(jasmine.Ajax.requests.count()).toBe(0);
req.abort();
return asyncTick();
}).then(() => {
blockResolver();
return p1;
}).then(() => {
fail();
}, () => {
expect(jasmine.Ajax.requests.count()).toBe(0);
});
});
});
describe('retries', () => {
beforeEach(() => {
jasmine.clock().install();
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('fails the request with "timedOut: true" if it times out without retries', done => {
const url = faker.internet.url();
const method = 'GET';
new SimpleWebRequest<string>(method, url, { timeout: 10, customErrorHandler: () => ErrorHandlingType.DoNotRetry })
.start()
.then(() => {
expect(false).toBeTruthy();
done();
})
.catch(errResp => {
expect(errResp.timedOut).toBeTruthy();
done();
});
asyncTick().then(() => {
jasmine.clock().tick(10);
});
});
it('timedOut flag is reset on retry', done => {
const url = faker.internet.url();
const method = 'GET';
const req = new SimpleWebRequest<string>(method, url, {
timeout: 10,
retries: 1,
customErrorHandler: () => ErrorHandlingType.RetryCountedWithBackoff,
});
const requestPromise = req.start();
requestPromise
.then(() => {
expect(false).toBeTruthy();
done();
})
.catch(errResp => {
expect(errResp.canceled).toBeTruthy();
expect(errResp.timedOut).toBeFalsy();
done();
});
// first try will time out, the second one will be aborted
asyncTick().then(() => {
jasmine.clock().tick(10);
req.abort();
});
});
});
// @TODO Add more unit tests
});