altair-graphql-core
Version:
Several of the core logic for altair graphql client
324 lines • 13.5 kB
JavaScript
import { describe, expect, it } from 'vitest';
import { buildResponse } from './response-builder';
import { MultiResponseStrategy } from './types';
const responseChunks = [
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["b"],"path":["alphabet",1]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["c"],"path":["alphabet",2]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["d"],"path":["alphabet",3]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["e"],"path":["alphabet",4]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"data":{"slowField":"This field resolves slowly after 5000ms ⏳"},"path":[]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["f"],"path":["alphabet",5]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["g"],"path":["alphabet",6]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["h"],"path":["alphabet",7]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["i"],"path":["alphabet",8]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["j"],"path":["alphabet",9]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["k"],"path":["alphabet",10]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["l"],"path":["alphabet",11]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["m"],"path":["alphabet",12]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["n"],"path":["alphabet",13]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["o"],"path":["alphabet",14]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["p"],"path":["alphabet",15]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["q"],"path":["alphabet",16]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["r"],"path":["alphabet",17]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["s"],"path":["alphabet",18]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["t"],"path":["alphabet",19]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["u"],"path":["alphabet",20]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["v"],"path":["alphabet",21]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["w"],"path":["alphabet",22]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["x"],"path":["alphabet",23]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["y"],"path":["alphabet",24]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["z"],"path":["alphabet",25]}],"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"hasNext":false}',
timestamp: 1718252802585,
},
];
describe('response-builder', () => {
it('unknown strategy should throw an error', () => {
expect(() => buildResponse(responseChunks, 'unknown')).toThrowError('Invalid strategy');
});
describe('concatenate strategy', () => {
it('should concatenate the responses', () => {
const res = buildResponse(responseChunks, MultiResponseStrategy.CONCATENATE);
expect(res).toMatchSnapshot();
});
it('should concatenate the responses and format the data if concatenated data is valid JSON', () => {
const res = buildResponse([
{
content: '{"hello":',
timestamp: 1718252802585,
},
{
content: '"world"}',
timestamp: 1718252802585,
},
]);
expect(res).toMatchSnapshot();
});
it('should return timestamp as 0 if no responses are provided', () => {
const res = buildResponse([], MultiResponseStrategy.CONCATENATE);
expect(res).toEqual([
{
content: '',
timestamp: 0,
json: false,
},
]);
});
});
describe('append strategy', () => {
it('should append the responses', () => {
const res = buildResponse(responseChunks, MultiResponseStrategy.APPEND);
expect(res).toMatchSnapshot();
});
});
describe('patch strategy', () => {
it('should patch the responses', () => {
const res = buildResponse(responseChunks, MultiResponseStrategy.PATCH);
expect(res).toMatchSnapshot();
});
it('should return empty list if no responses are provided', () => {
const res = buildResponse([], MultiResponseStrategy.PATCH);
expect(res).toEqual([]);
});
it('should throw an error if the first response is not JSON', () => {
expect(() => buildResponse([
{
content: 'This is not a JSON object',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.PATCH)).toThrowError('JSON response required for patching!');
});
it('should throw an error if subsequent responses are not JSON', () => {
expect(() => buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: 'This is not a JSON object',
timestamp: 1718252802585,
},
], MultiResponseStrategy.PATCH)).toThrowError('JSON response required for patching!');
});
it('should gather all errors from all responses', () => {
const res = buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true,"errors":[{"message":"Error 1"}]}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0],"errors":[{"message":"Incremental error"}]}],"hasNext":true,"errors":[{"message":"Error 2"}]}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["b"],"path":["alphabet",1]}],"hasNext":true,"errors":[{"message":"Error 3"}]}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.PATCH);
expect(res).toMatchSnapshot();
});
it('should handle incremental payloads in the old format', () => {
const res = buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"data":{"additionalField":"This field is added in the second response"},"path":["inner"]}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.PATCH);
expect(res).toMatchSnapshot();
});
it('should patch responses with extensions', () => {
const res = buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true,"extensions":{"key":"value"}}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["b"],"path":["alphabet",1],"extensions":{"key1":"value1"}}],"hasNext":true}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.PATCH);
expect(res).toMatchSnapshot();
});
it('should throw an error for incremental payload with invalid @stream path', () => {
expect(() => buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet"]}],"hasNext":true}',
timestamp: 1718252802585,
},
])).toThrowError('Path for stream incremental payload should end with a number!');
});
});
describe('auto strategy', () => {
it('should concatenate the responses when the first response is not a JSON object', () => {
const res = buildResponse([
{
content: 'This is not a JSON object',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.AUTO);
expect(res).toMatchSnapshot();
});
it('should return timestamp as 0 if no responses are provided', () => {
const res = buildResponse([], MultiResponseStrategy.AUTO);
expect(res).toEqual([
{
content: '',
timestamp: 0,
json: false,
},
]);
});
it('should patch the responses when the first response is patchable', () => {
const res = buildResponse([
{
content: '{"data":{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"},"hasNext":true}',
timestamp: 1718252802585,
},
{
content: '{"incremental":[{"items":["a"],"path":["alphabet",0]}],"hasNext":true}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.AUTO);
expect(res).toMatchSnapshot();
});
// https://github.com/felipe-gdr/spring-graphql-defer/issues/5
it('should patch the responses when patchable - sample 2', () => {
const res = buildResponse([
{
content: `{"data":{"bookById":{"name":"Effective Java"}},"hasNext":true}`,
timestamp: 1718252802585,
},
{
content: `{"hasNext":true,"incremental":[{"path":["bookById"],"data":{"author":{"firstName":"Joshua"}}}]}`,
timestamp: 1718252802585,
},
{
content: `{"hasNext":false,"incremental":[{"path":[],"data":{"book2":{"name":"Hitchhiker's Guide to the Galaxy"}}}]}`,
timestamp: 1718252802585,
},
]);
expect(res).toMatchSnapshot();
});
it('should append the responses when the first response is a JSON object but not patchable', () => {
const res = buildResponse([
{
content: '{"hello":"Hello world","alphabet":[],"fastField":"This field resolves fast! ⚡️"}',
timestamp: 1718252802585,
},
{
content: '{"hi": "Hi world"}',
timestamp: 1718252802585,
},
], MultiResponseStrategy.AUTO);
expect(res).toMatchSnapshot();
});
});
});
//# sourceMappingURL=response-builder.spec.js.map