feat: collection schema definition

This commit is contained in:
Anoop M D 2022-10-15 01:15:56 +05:30
parent 013f9f9e3d
commit b3bf29d6b2
6 changed files with 350 additions and 1 deletions

View File

@ -49,7 +49,7 @@ export const loadWorkspacesFromIdb = () => (dispatch) => {
export const addWorkspace = (workspaceName) => (dispatch) => { export const addWorkspace = (workspaceName) => (dispatch) => {
const newWorkspace = { const newWorkspace = {
uid: uuid() + "junk", uid: uuid(),
name: workspaceName name: workspaceName
}; };

View File

@ -0,0 +1,64 @@
const Yup = require('yup');
const { uidSchema } = require("../common");
const keyValueSchema = Yup.object({
uid: uidSchema,
name: Yup.string().nullable().max(256, 'name must be 256 characters or less').defined(),
value: Yup.string().nullable().max(2048, 'value must be 2048 characters or less').defined(),
description: Yup.string().nullable().max(2048, 'description must be 2048 characters or less').defined(),
enabled: Yup.boolean().defined()
}).noUnknown(true).strict();
const requestTypeSchema = Yup.string().oneOf(['http', 'graphql']).required('type is required');
const requestUrlSchema = Yup.string().min(0).max(2048, 'name must be 2048 characters or less').defined();
const requestMethodSchema = Yup.string().oneOf(['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD']).required('method is required');
const requestBodySchema = Yup.object({
mode: Yup.string().oneOf(['none', 'json', 'text', 'xml', 'formUrlEncoded', 'multipartForm']).required('mode is required'),
json: Yup.string().max(10240, 'json must be 10240 characters or less'),
text: Yup.string().max(10240, 'text must be 10240 characters or less'),
xml: Yup.string().max(10240, 'xml must be 10240 characters or less'),
formUrlEncoded: keyValueSchema,
multipartForm: keyValueSchema,
}).noUnknown(true).strict();
// Right now, the request schema is very tightly coupled with http request
// As we introduce more request types in the future, we will improve the definition to support
// schema structure based on other request type
const requestSchema = Yup.object({
type: requestTypeSchema,
url: requestUrlSchema,
method: requestMethodSchema,
headers: Yup.array().of(keyValueSchema).required('headers are required'),
params: Yup.array().of(keyValueSchema).required('params are required'),
body: requestBodySchema
}).noUnknown(true).strict();
const itemSchema = Yup.object({
uid: uidSchema,
type: Yup.string().oneOf(['request', 'folder']).required('type is required'),
name: Yup.string()
.min(1, 'name must be atleast 1 characters')
.max(50, 'name must be 100 characters or less')
.required('name is required'),
request: requestSchema.when('type', {
is: 'request',
then: (schema) => schema.required('request is required when item-type is request')
}),
items: Yup.lazy(() => Yup.array().of(itemSchema))
}).noUnknown(true).strict();
const collectionSchema = Yup.object({
uid: uidSchema,
name: Yup.string()
.min(1, 'name must be atleast 1 characters')
.max(50, 'name must be 100 characters or less')
.required('name is required'),
items: Yup.array().of(itemSchema)
}).noUnknown(true).strict();
module.exports = {
requestSchema,
itemSchema,
collectionSchema
};

View File

@ -0,0 +1,120 @@
const { expect } = require('@jest/globals');
const { uuid } = require("../utils/testUtils");
const { collectionSchema } = require("./index");
describe('Collection Schema Validation', () => {
it('collection schema must validate successfully - simple collection, no items', async () => {
const collection = {
uid: uuid(),
name: 'My Collection'
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
it('collection schema must validate successfully - simple collection, empty items', async () => {
const collection = {
uid: uuid(),
name: 'My Collection',
items: []
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
it('collection schema must validate successfully - simple collection, just a folder item', async () => {
const collection = {
uid: uuid(),
name: 'My Collection',
items: [{
uid: uuid(),
name: 'A Folder',
type: 'folder'
}]
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
it('collection schema must validate successfully - simple collection, just a request item', async () => {
const collection = {
uid: uuid(),
name: 'My Collection',
items: [{
uid: uuid(),
name: 'Get Countries',
type: 'request',
request: {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [],
params: [],
body: {
mode: 'none'
}
}
}]
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
it('collection schema must validate successfully - simple collection, folder inside folder', async () => {
const collection = {
uid: uuid(),
name: 'My Collection',
items: [{
uid: uuid(),
name: 'First Level Folder',
type: 'folder',
items: [{
uid: uuid(),
name: 'Second Level Folder',
type: 'folder'
}]
}]
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
it('collection schema must validate successfully - simple collection, [folder] [request + folder]', async () => {
const collection = {
uid: uuid(),
name: 'My Collection',
items: [{
uid: uuid(),
name: 'First Level Folder',
type: 'folder',
items: [{
uid: uuid(),
name: 'Get Countries',
type: 'request',
request: {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [],
params: [],
body: {
mode: 'none'
}
}
}, {
uid: uuid(),
name: 'Second Level Folder',
type: 'folder'
}]
}]
};
const isValid = await collectionSchema.validate(collection);
expect(isValid).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
const { expect } = require('@jest/globals');
const { uuid, validationErrorWithMessages } = require("../utils/testUtils");
const { itemSchema } = require("./index");
describe('Item Schema Validation', () => {
it('item schema must validate successfully - simple items', async () => {
const item = {
uid: uuid(),
name: 'A Folder',
type: 'folder'
};
const isValid = await itemSchema.validate(item);
expect(isValid).toBeTruthy();
});
it('item schema must throw an error if name is missing', async () => {
const item = {
uid: uuid(),
type: 'folder'
};
return Promise.all([
expect(itemSchema.validate(item)).rejects.toEqual(
validationErrorWithMessages('name is required')
)
]);
});
it('item schema must throw an error if name is empty', async () => {
const item = {
uid: uuid(),
name: '',
type: 'folder'
};
return Promise.all([
expect(itemSchema.validate(item)).rejects.toEqual(
validationErrorWithMessages('name must be atleast 1 characters')
)
]);
});
it('item schema must throw an error if request is not present when item-type is request', async () => {
const item = {
uid: uuid(),
name: 'Get Users',
type: 'request'
};
return Promise.all([
expect(itemSchema.validate(item)).rejects.toEqual(
validationErrorWithMessages('request is required when item-type is request')
)
]);
});
});

View File

@ -0,0 +1,107 @@
const { expect } = require('@jest/globals');
const { uuid, validationErrorWithMessages } = require("../utils/testUtils");
const { requestSchema } = require("./index");
describe('Request Schema Validation', () => {
it('request schema must validate successfully - simple request', async () => {
const request = {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [],
params: [],
body: {
mode: 'none'
}
};
const isValid = await requestSchema.validate(request);
expect(isValid).toBeTruthy();
});
it('request schema must throw an error of type is invalid', async () => {
const request = {
type: 'http-junk',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [],
params: [],
body: {
mode: 'none'
}
};
return Promise.all([
expect(requestSchema.validate(request)).rejects.toEqual(
validationErrorWithMessages('type must be one of the following values: http, graphql')
)
]);
});
it('request schema must throw an error of method is invalid', async () => {
const request = {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET-junk',
headers: [],
params: [],
body: {
mode: 'none'
}
};
return Promise.all([
expect(requestSchema.validate(request)).rejects.toEqual(
validationErrorWithMessages('method must be one of the following values: GET, POST, PUT, DELETE, PATCH, HEAD')
)
]);
});
it('request schema must throw an error of header name is missing', async () => {
const request = {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [{
uid: uuid(),
value: 'Check',
description: '',
enabled: true
}],
params: [],
body: {
mode: 'none'
}
};
return Promise.all([
expect(requestSchema.validate(request)).rejects.toEqual(
validationErrorWithMessages('headers[0].name must be defined')
)
]);
});
it('request schema must throw an error of param value is missing', async () => {
const request = {
type: 'http',
url: 'https://restcountries.com/v2/alpha/in',
method: 'GET',
headers: [],
params: [{
uid: uuid(),
name: 'customerId',
description: '',
enabled: true
}],
body: {
mode: 'none'
}
};
return Promise.all([
expect(requestSchema.validate(request)).rejects.toEqual(
validationErrorWithMessages('params[0].value must be defined')
)
]);
});
});

View File

@ -3,6 +3,7 @@ const Yup = require('yup');
const uidSchema = Yup.string() const uidSchema = Yup.string()
.length(21, 'uid must be 21 characters in length') .length(21, 'uid must be 21 characters in length')
.matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric') .matches(/^[a-zA-Z0-9]*$/, 'uid must be alphanumeric')
.required('uid is required')
.strict(); .strict();
module.exports = { module.exports = {