sp-dev-fx-webparts/samples/react-graph-auto-batching/tests/dal/http/BatchGraphClient.test.ts

749 lines
26 KiB
TypeScript

///<reference types="jest" />
import { BatchGraphClient } from "../../../src/dal/http/BatchGraphClient";
import { assert } from "chai";
import { TestingUtilities } from "../../TestingUtilities";
import { BatchHandler } from "../../../src/dal/http/BatchHandler";
describe("BatchGraphClient", ()=>{
test("should batch get requests", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
});
}
}
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
});
test("should request two batches", async () => {
let postMethod = jest.fn()
postMethod.mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
})).mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User second batch"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id second batch"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
}));
let baseClient = {
post: postMethod
};
let batchClient = new BatchGraphClient(baseClient as any, 10);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
await TestingUtilities.sleep(11);
let secondMePromise = batchClient.get("/me");
let secondGroupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
let secondMe = await secondMePromise;
let secondGroup = await secondGroupPromise;
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
assert.deepEqual((await secondMe.json()), {
displayName: "Test User second batch"
});
assert.deepEqual((await secondGroup.json()), {
value: [{
id: "Test group id second batch"
}]
});
});
test("should batch get requests", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
},
{
status: 404,
body: {
error: {
id: "group-error"
}
},
id: encodeURIComponent("/me/groups")
},
],
})
});
}
}
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
try {
await groupPromise;
}
catch (err) {
assert.deepEqual((await err.json()), {
error: {
id: "group-error"
}
});
}
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
});
test("should batch v1 request separately", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
if (url.indexOf("/v1.0/") >= 0) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User v1"
},
id: encodeURIComponent("/v1.0/me")
}
],
})
});
}
else {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User Beta"
},
id: encodeURIComponent("/me")
}
],
})
})
}
}
}
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let meV1Promise = batchClient.get("/v1.0/me");
Promise.all([mePromise, meV1Promise]);
let betaMe = await (await mePromise).json();
let meV1 = await (await meV1Promise).json();
assert.equal(meV1.displayName, "Test User v1");
assert.equal(betaMe.displayName, "Test User Beta");
});
test("should batch v1 request separately (different resources)", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
if (url.indexOf("/v1.0/") >= 0) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User v1"
},
id: encodeURIComponent("/v1.0/some-other-resource")
}
],
})
});
}
else {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User Beta"
},
id: encodeURIComponent("/me")
}
],
})
})
}
}
}
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let meV1Promise = batchClient.get("/v1.0/some-other-resource");
Promise.all([mePromise, meV1Promise]);
let betaMe = await (await mePromise).json();
let meV1 = await (await meV1Promise).json();
assert.equal(meV1.displayName, "Test User v1");
assert.equal(betaMe.displayName, "Test User Beta");
});
test("should retry single request", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return {}
}
}
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
}));
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
}
],
})
}));
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
});
test("should retry single request to v1", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return {}
}
}
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
}));
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/v1.0/me")
}
],
})
}));
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/v1.0/me")
}
],
})
}));
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/v1.0/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
});
test("should retry 5 times and error request", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return {}
}
}
const throttlePromise = Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/me")
}
],
})
})
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
})).mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
const batchHandlerSpy = jest.spyOn(BatchHandler.prototype, "executeBatch")
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
expect(batchHandlerSpy).toBeCalledTimes(6);
assert.isFalse(me.ok);
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
batchHandlerSpy.mockClear();
});
test("should retry 5 times and error request (next request should work)", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return {}
}
}
const throttlePromise = Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/me")
}
],
})
});
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
})).mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(throttlePromise)
.mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
body: {
displayName: "Test User"
},
status: 200,
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
},
],
})
}));
const batchHandlerSpy = jest.spyOn(BatchHandler.prototype, "executeBatch")
let batchClient = new BatchGraphClient(baseClient as any);
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let me = await mePromise;
let group = await groupPromise;
expect(batchHandlerSpy).toBeCalledTimes(6);
assert.isFalse(me.ok);
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
mePromise = batchClient.get("/me");
groupPromise = batchClient.get("/me/groups");
me = await mePromise;
group = await groupPromise;
assert.isTrue(me.ok);
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
});
test("should retry and concurrent request should be called", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return {}
}
}
const throttlePromise = Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 429,
body: {
error: "Throttle"
},
id: encodeURIComponent("/me")
}
],
})
});
//So...the first request should be throttled
jest.spyOn(baseClient, 'post').mockReturnValueOnce(throttlePromise);
//The first retry will occur before we call concurrent request, to make sure retry batch will not be overridden
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
}
],
})
}));
//The concurrent request should be resolved with this response
jest.spyOn(baseClient, 'post').mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User (concurrent)"
},
id: encodeURIComponent("/me-concurrent")
}
],
})
}));
let batchClient = new BatchGraphClient(baseClient as any, 10);
//request throttled endpoint
let throttledPromise = batchClient.get("/me");
//wait 12 milliseconds to be sure retry batch is constructed
await TestingUtilities.sleep(12);
//then request another
let concurrentRequest = batchClient.get("/me-concurrent");
let meResponse = await throttledPromise;
let concurrentMeResponse = await concurrentRequest;
assert.equal((await meResponse.json()).displayName, "Test User");
assert.equal((await concurrentMeResponse.json()).displayName, "Test User (concurrent)");
});
test("should split bigger batch to subsequent requests", async () => {
let baseClient = {
get: (url, options) => {
throw "Don't call get!"
},
post: (url, options) => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
],
})
});
}
}
jest.spyOn(baseClient, "post").mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User"
},
id: encodeURIComponent("/me")
},
{
status: 200,
body: {
value: [{
id: "Test group id"
}]
},
id: encodeURIComponent("/me/groups")
}
],
})
}));
jest.spyOn(baseClient, "post").mockReturnValueOnce(Promise.resolve({
ok: true,
json: () => Promise.resolve({
responses: [
{
status: 200,
body: {
displayName: "Test User 2"
},
id: encodeURIComponent("/me2")
},
{
status: 200,
body: {
value: [{
id: "Test group id 2"
}]
},
id: encodeURIComponent("/me/groups2")
}
],
})
}));
let batchClient = new BatchGraphClient(baseClient as any);
batchClient.batchSplitThreshold = 2;
let mePromise = batchClient.get("/me");
let groupPromise = batchClient.get("/me/groups");
let mePromise2 = batchClient.get("/me2");
let groupPromise2 = batchClient.get("/me/groups2");
let me = await mePromise;
let group = await groupPromise;
let me2 = await mePromise2;
let group2 = await groupPromise2;
assert.deepEqual((await me.json()), {
displayName: "Test User"
});
assert.deepEqual((await group.json()), {
value: [{
id: "Test group id"
}]
});
assert.deepEqual((await me2.json()), {
displayName: "Test User 2"
});
assert.deepEqual((await group2.json()), {
value: [{
id: "Test group id 2"
}]
});
});
});