diff --git a/packages/core/src/models/artifact.ts b/packages/core/src/models/artifact.ts index 015459ce..48c9cf33 100644 --- a/packages/core/src/models/artifact.ts +++ b/packages/core/src/models/artifact.ts @@ -18,6 +18,12 @@ error: message: 'You are not allowed to access this resource' */ +export enum PaginationMode { + CURSOR = 'CURSOR', + OFFSET = 'OFFSET', + KEYSET = 'KEYSET', +} + export enum FieldInType { QUERY = 'QUERY', HEADER = 'HEADER', @@ -44,6 +50,12 @@ export interface RequestSchema { validators: Array; } +export interface PaginationSchema { + mode: PaginationMode; + // The key name used for do filtering by key for keyset pagination. + keyName?: string; +} + export interface ErrorInfo { code: string; message: string; @@ -59,6 +71,9 @@ export interface APISchema { request: Array; errors: Array; response: any; + // The pagination strategy that do paginate when querying + // If not set pagination, then API request not provide the field to do it + pagination?: PaginationSchema; } export interface BuiltArtifact { diff --git a/packages/serve/src/lib/data-query/builder/dataQueryBuilder.ts b/packages/serve/src/lib/data-query/builder/dataQueryBuilder.ts index 4736ede2..31dcde55 100644 --- a/packages/serve/src/lib/data-query/builder/dataQueryBuilder.ts +++ b/packages/serve/src/lib/data-query/builder/dataQueryBuilder.ts @@ -1,3 +1,6 @@ +import { IDataSource } from '@data-source/.'; +import { Pagination } from '@route/.'; + import { find, isEmpty } from 'lodash'; import { ComparisonPredicate, @@ -177,6 +180,8 @@ export interface SQLClauseOperation { export interface IDataQueryBuilder { readonly statement: string; readonly operations: SQLClauseOperation; + readonly dataSource: IDataSource; + // Select clause methods select(...columns: Array): DataQueryBuilder; distinct(...columns: Array): DataQueryBuilder; @@ -386,21 +391,29 @@ export interface IDataQueryBuilder { limit(size: number): DataQueryBuilder; offset(move: number): DataQueryBuilder; take(size: number, move: number): DataQueryBuilder; + // paginate + paginate(pagination: Pagination): void; + value(): Promise; + clone(): IDataQueryBuilder; } export class DataQueryBuilder implements IDataQueryBuilder { public readonly statement: string; // record all operations for different SQL clauses public readonly operations: SQLClauseOperation; - + public readonly dataSource: IDataSource; + public pagination?: Pagination; constructor({ statement, operations, + dataSource, }: { statement: string; operations?: SQLClauseOperation; + dataSource: IDataSource; }) { this.statement = statement; + this.dataSource = dataSource; this.operations = operations || { select: null, where: [], @@ -601,6 +614,7 @@ export class DataQueryBuilder implements IDataQueryBuilder { public whereWrapped(builderCallback: BuilderClauseCallback) { const wrappedBuilder = new DataQueryBuilder({ statement: '', + dataSource: this.dataSource, }); builderCallback(wrappedBuilder); this.recordWhere({ @@ -1043,6 +1057,33 @@ export class DataQueryBuilder implements IDataQueryBuilder { return this; } + public clone() { + return new DataQueryBuilder({ + statement: this.statement, + dataSource: this.dataSource, + operations: this.operations, + }); + } + + // setup pagination if would like to do paginate + public paginate(pagination: Pagination) { + this.pagination = pagination; + } + + public async value() { + // call data source + const result = await this.dataSource.execute({ + statement: this.statement, + operations: this.operations, + pagination: this.pagination, + }); + + // Reset operations + await this.resetOperations(); + + return result; + } + // record Select-On related operations private recordSelect({ command, @@ -1128,10 +1169,4 @@ export class DataQueryBuilder implements IDataQueryBuilder { this.operations.limit = null; this.operations.offset = null; } - public value() { - // TODO: call Driver - - // Reset operations - this.resetOperations(); - } } diff --git a/packages/serve/src/lib/data-query/factory.ts b/packages/serve/src/lib/data-query/factory.ts index b00543b7..c5184bce 100644 --- a/packages/serve/src/lib/data-query/factory.ts +++ b/packages/serve/src/lib/data-query/factory.ts @@ -1,10 +1,15 @@ +import { IDataSource } from '@data-source/.'; import { DataQueryBuilder, IDataQueryBuilder, } from './builder/dataQueryBuilder'; -export const dataQuery = (sqlStatement: string): IDataQueryBuilder => { +export const dataQuery = ( + sqlStatement: string, + dataSource: IDataSource +): IDataQueryBuilder => { return new DataQueryBuilder({ statement: sqlStatement, + dataSource: dataSource, }); }; diff --git a/packages/serve/src/lib/data-source/dataSource.ts b/packages/serve/src/lib/data-source/dataSource.ts new file mode 100644 index 00000000..2e717128 --- /dev/null +++ b/packages/serve/src/lib/data-source/dataSource.ts @@ -0,0 +1,14 @@ +import { SQLClauseOperation } from '@data-query/.'; +import { Pagination } from '@route/.'; + +export interface IDataSource { + execute({ + statement, + operations, + pagination, + }: { + statement: string; + operations: SQLClauseOperation; + pagination?: Pagination; + }): Promise; +} diff --git a/packages/serve/src/lib/data-source/index.ts b/packages/serve/src/lib/data-source/index.ts new file mode 100644 index 00000000..f80a6659 --- /dev/null +++ b/packages/serve/src/lib/data-source/index.ts @@ -0,0 +1 @@ +export * from './dataSource'; diff --git a/packages/serve/src/lib/pagination/index.ts b/packages/serve/src/lib/pagination/index.ts new file mode 100644 index 00000000..b7b40acd --- /dev/null +++ b/packages/serve/src/lib/pagination/index.ts @@ -0,0 +1 @@ +export * from './strategy'; diff --git a/packages/serve/src/lib/pagination/strategy/cursorBasedStrategy.ts b/packages/serve/src/lib/pagination/strategy/cursorBasedStrategy.ts new file mode 100644 index 00000000..e6b710cc --- /dev/null +++ b/packages/serve/src/lib/pagination/strategy/cursorBasedStrategy.ts @@ -0,0 +1,26 @@ +import { KoaRouterContext } from '@route/route-component'; +import { normalizeStringValue, PaginationMode } from '@vulcan/core'; +import { PaginationStrategy } from './strategy'; + +export interface CursorPagination { + limit: number; + cursor: string; +} + +export class CursorBasedStrategy extends PaginationStrategy { + public async transform(ctx: KoaRouterContext) { + const checkFelidInHeader = ['limit', 'cursor'].every((field) => + Object.keys(ctx.request.query).includes(field) + ); + if (!checkFelidInHeader) + throw new Error( + `The ${PaginationMode.CURSOR} must provide limit and cursor in query string.` + ); + const limitVal = ctx.request.query['limit'] as string; + const cursorVal = ctx.request.query['cursor'] as string; + return { + limit: normalizeStringValue(limitVal, 'limit', Number.name), + cursor: normalizeStringValue(cursorVal, 'cursor', Number.name), + } as CursorPagination; + } +} diff --git a/packages/serve/src/lib/pagination/strategy/index.ts b/packages/serve/src/lib/pagination/strategy/index.ts new file mode 100644 index 00000000..a399285c --- /dev/null +++ b/packages/serve/src/lib/pagination/strategy/index.ts @@ -0,0 +1,4 @@ +export * from './strategy'; +export * from './offsetBasedStrategy'; +export * from './cursorBasedStrategy'; +export * from './keysetBasedStrategy'; diff --git a/packages/serve/src/lib/pagination/strategy/keysetBasedStrategy.ts b/packages/serve/src/lib/pagination/strategy/keysetBasedStrategy.ts new file mode 100644 index 00000000..834af477 --- /dev/null +++ b/packages/serve/src/lib/pagination/strategy/keysetBasedStrategy.ts @@ -0,0 +1,41 @@ +import { + APISchema, + normalizeStringValue, + PaginationMode, + PaginationSchema, +} from '@vulcan/core'; +import { KoaRouterContext } from '@route/route-component'; +import { PaginationStrategy } from './strategy'; + +export interface KeysetPagination { + limit: number; + [keyName: string]: string | number; +} + +export class KeysetBasedStrategy extends PaginationStrategy { + private pagination: PaginationSchema; + constructor(pagination: PaginationSchema) { + super(); + this.pagination = pagination; + } + public async transform(ctx: KoaRouterContext) { + if (!this.pagination.keyName) + throw new Error( + `The keyset pagination need to set "keyName" in schema for indicate what key need to do filter.` + ); + const { keyName } = this.pagination; + const checkFelidInHeader = ['limit', keyName].every((field) => + Object.keys(ctx.request.query).includes(field) + ); + if (!checkFelidInHeader) + throw new Error( + `The ${PaginationMode.KEYSET} must provide limit and offset in query string.` + ); + const limitVal = ctx.request.query['limit'] as string; + const keyNameVal = ctx.request.query[keyName] as string; + return { + limit: normalizeStringValue(limitVal, 'limit', Number.name), + [keyName]: normalizeStringValue(keyNameVal, keyName, Number.name), + } as KeysetPagination; + } +} diff --git a/packages/serve/src/lib/pagination/strategy/offsetBasedStrategy.ts b/packages/serve/src/lib/pagination/strategy/offsetBasedStrategy.ts new file mode 100644 index 00000000..29e3a098 --- /dev/null +++ b/packages/serve/src/lib/pagination/strategy/offsetBasedStrategy.ts @@ -0,0 +1,26 @@ +import { normalizeStringValue, PaginationMode } from '@vulcan/core'; +import { KoaRouterContext } from '@route/route-component'; +import { PaginationStrategy } from './strategy'; + +export interface OffsetPagination { + limit: number; + offset: number; +} + +export class OffsetBasedStrategy extends PaginationStrategy { + public async transform(ctx: KoaRouterContext) { + const checkFelidInHeader = ['limit', 'offset'].every((field) => + Object.keys(ctx.request.query).includes(field) + ); + if (!checkFelidInHeader) + throw new Error( + `The ${PaginationMode.OFFSET} must provide limit and offset in query string.` + ); + const limitVal = ctx.request.query['limit'] as string; + const offsetVal = ctx.request.query['offset'] as string; + return { + limit: normalizeStringValue(limitVal, 'limit', Number.name), + offset: normalizeStringValue(offsetVal, 'offset', Number.name), + } as OffsetPagination; + } +} diff --git a/packages/serve/src/lib/pagination/strategy/strategy.ts b/packages/serve/src/lib/pagination/strategy/strategy.ts new file mode 100644 index 00000000..c3ad6fdb --- /dev/null +++ b/packages/serve/src/lib/pagination/strategy/strategy.ts @@ -0,0 +1,5 @@ +import { KoaRouterContext } from '@route/route-component'; + +export abstract class PaginationStrategy { + public abstract transform(ctx: KoaRouterContext): Promise; +} diff --git a/packages/serve/src/lib/route/route-component/baseRoute.ts b/packages/serve/src/lib/route/route-component/baseRoute.ts index 24772252..6b791f88 100644 --- a/packages/serve/src/lib/route/route-component/baseRoute.ts +++ b/packages/serve/src/lib/route/route-component/baseRoute.ts @@ -3,47 +3,52 @@ import { RouterContext as KoaRouterContext } from 'koa-router'; import { IRequestTransformer, RequestParameters } from './requestTransformer'; import { IRequestValidator } from './requestValidator'; import { APISchema, TemplateEngine } from '@vulcan/core'; +import { IPaginationTransformer, Pagination } from './paginationTransformer'; export { KoaRouterContext, KoaNext }; +interface TransformedRequest { + reqParams: RequestParameters; + pagination?: Pagination; +} + interface IRoute { respond(ctx: KoaRouterContext): Promise; } export abstract class BaseRoute implements IRoute { public readonly apiSchema: APISchema; - private readonly reqTransformer: IRequestTransformer; - private readonly reqValidator: IRequestValidator; - private readonly templateEngine: TemplateEngine; - + protected readonly reqTransformer: IRequestTransformer; + protected readonly reqValidator: IRequestValidator; + protected readonly templateEngine: TemplateEngine; + protected readonly paginationTransformer: IPaginationTransformer; constructor({ apiSchema, reqTransformer, reqValidator, + paginationTransformer, templateEngine, }: { apiSchema: APISchema; reqTransformer: IRequestTransformer; reqValidator: IRequestValidator; + paginationTransformer: IPaginationTransformer; templateEngine: TemplateEngine; }) { this.apiSchema = apiSchema; this.reqTransformer = reqTransformer; this.reqValidator = reqValidator; + this.paginationTransformer = paginationTransformer; this.templateEngine = templateEngine; } - public async respond(ctx: KoaRouterContext) { - const params = await this.reqTransformer.transform(ctx, this.apiSchema); - await this.reqValidator.validate(params, this.apiSchema); - return await this.handleRequest(ctx, params); - } + public abstract respond(ctx: KoaRouterContext): Promise; - protected abstract handleRequest( - ctx: KoaRouterContext, - reqParams: RequestParameters - ): Promise; + protected abstract prepare( + ctx: KoaRouterContext + ): Promise; - protected async runQuery(reqParams: RequestParameters) { + protected async handle(transformed: TransformedRequest) { + const { reqParams } = transformed; // could template name or template path, use for template engine const { templateSource } = this.apiSchema; const statement = await this.templateEngine.render( diff --git a/packages/serve/src/lib/route/route-component/graphQLRoute.ts b/packages/serve/src/lib/route/route-component/graphQLRoute.ts index 60da86ba..d25703ac 100644 --- a/packages/serve/src/lib/route/route-component/graphQLRoute.ts +++ b/packages/serve/src/lib/route/route-component/graphQLRoute.ts @@ -2,6 +2,7 @@ import { APISchema, TemplateEngine } from '@vulcan/core'; import { IRequestTransformer, RequestParameters } from './requestTransformer'; import { IRequestValidator } from './requestValidator'; import { BaseRoute, KoaRouterContext } from './baseRoute'; +import { IPaginationTransformer, Pagination } from './paginationTransformer'; export class GraphQLRoute extends BaseRoute { public readonly operationName: string; @@ -10,14 +11,22 @@ export class GraphQLRoute extends BaseRoute { apiSchema, reqTransformer, reqValidator, + paginationTransformer, templateEngine, }: { apiSchema: APISchema; reqTransformer: IRequestTransformer; reqValidator: IRequestValidator; + paginationTransformer: IPaginationTransformer; templateEngine: TemplateEngine; }) { - super({ apiSchema, reqTransformer, reqValidator, templateEngine }); + super({ + apiSchema, + reqTransformer, + reqValidator, + paginationTransformer, + templateEngine, + }); this.operationName = apiSchema.operationName; } @@ -26,11 +35,21 @@ export class GraphQLRoute extends BaseRoute { // TODO: generate graphql type by api schema } - protected async handleRequest( - ctx: KoaRouterContext, - reqParams: RequestParameters - ) { - // TODO: implement query by dataset - return reqParams; + protected async prepare(ctx: KoaRouterContext) { + /** + * TODO: the graphql need to transform from body. + * Therefore, current request and pagination transformer not suitable (need to provide another graphql transform method or class) + */ + + return { + reqParams: {}, + }; + } + + public async respond(ctx: KoaRouterContext) { + const transformed = await this.prepare(ctx); + await this.handle(transformed); + // TODO: get template engine handled result and return response by checking API schema + return transformed; } } diff --git a/packages/serve/src/lib/route/route-component/index.ts b/packages/serve/src/lib/route/route-component/index.ts index 68e3312f..9bbdf465 100644 --- a/packages/serve/src/lib/route/route-component/index.ts +++ b/packages/serve/src/lib/route/route-component/index.ts @@ -1,5 +1,6 @@ export * from './requestValidator'; export * from './requestTransformer'; +export * from './paginationTransformer'; export * from './baseRoute'; export * from './restfulRoute'; export * from './graphQLRoute'; diff --git a/packages/serve/src/lib/route/route-component/paginationTransformer.ts b/packages/serve/src/lib/route/route-component/paginationTransformer.ts new file mode 100644 index 00000000..374c9923 --- /dev/null +++ b/packages/serve/src/lib/route/route-component/paginationTransformer.ts @@ -0,0 +1,38 @@ +import { KoaRouterContext } from '@route/route-component'; +import { APISchema, PaginationMode } from '@vulcan/core'; +import { + CursorPagination, + OffsetPagination, + KeysetPagination, + CursorBasedStrategy, + OffsetBasedStrategy, + KeysetBasedStrategy, +} from '@pagination/.'; + +export type Pagination = CursorPagination | OffsetPagination | KeysetPagination; + +export interface IPaginationTransformer { + transform( + ctx: KoaRouterContext, + apiSchema: APISchema + ): Promise; +} +export class PaginationTransformer { + public async transform(ctx: KoaRouterContext, apiSchema: APISchema) { + const { pagination } = apiSchema; + + if (pagination) { + if (!(pagination.mode in PaginationMode)) + throw new Error( + `The pagination only support ${Object.keys(PaginationMode)}` + ); + const strategyMapper = { + [PaginationMode.OFFSET]: new OffsetBasedStrategy().transform, + [PaginationMode.CURSOR]: new CursorBasedStrategy().transform, + [PaginationMode.KEYSET]: new KeysetBasedStrategy(pagination).transform, + }; + return await strategyMapper[pagination.mode](ctx); + } + return undefined; + } +} diff --git a/packages/serve/src/lib/route/route-component/restfulRoute.ts b/packages/serve/src/lib/route/route-component/restfulRoute.ts index 6bb9ddf8..a3cd0fa0 100644 --- a/packages/serve/src/lib/route/route-component/restfulRoute.ts +++ b/packages/serve/src/lib/route/route-component/restfulRoute.ts @@ -1,8 +1,8 @@ import { APISchema, TemplateEngine } from '@vulcan/core'; -import { IRequestTransformer, RequestParameters } from './requestTransformer'; +import { IRequestTransformer } from './requestTransformer'; import { IRequestValidator } from './requestValidator'; import { BaseRoute, KoaRouterContext } from './baseRoute'; - +import { IPaginationTransformer } from './paginationTransformer'; export class RestfulRoute extends BaseRoute { public readonly urlPath: string; @@ -10,24 +10,48 @@ export class RestfulRoute extends BaseRoute { apiSchema, reqTransformer, reqValidator, + paginationTransformer, templateEngine, }: { apiSchema: APISchema; reqTransformer: IRequestTransformer; reqValidator: IRequestValidator; + paginationTransformer: IPaginationTransformer; templateEngine: TemplateEngine; }) { - super({ apiSchema, reqTransformer, reqValidator, templateEngine }); + super({ + apiSchema, + reqTransformer, + reqValidator, + paginationTransformer, + templateEngine, + }); this.urlPath = apiSchema.urlPath; } - protected async handleRequest( - ctx: KoaRouterContext, - reqParams: RequestParameters - ) { - // TODO: implement query by dataset - ctx.response.body = reqParams; - return; + public async respond(ctx: KoaRouterContext) { + const transformed = await this.prepare(ctx); + await this.handle(transformed); + // TODO: get template engine handled result and return response by checking API schema + ctx.response.body = { + ...transformed, + }; + } + + protected async prepare(ctx: KoaRouterContext) { + // get request data from context + const reqParams = await this.reqTransformer.transform(ctx, this.apiSchema); + // validate request format + await this.reqValidator.validate(reqParams, this.apiSchema); + // get pagination data from context + const pagination = await this.paginationTransformer.transform( + ctx, + this.apiSchema + ); + return { + reqParams, + pagination, + }; } } diff --git a/packages/serve/src/lib/route/routeGenerator.ts b/packages/serve/src/lib/route/routeGenerator.ts index b05b325c..50e88661 100644 --- a/packages/serve/src/lib/route/routeGenerator.ts +++ b/packages/serve/src/lib/route/routeGenerator.ts @@ -1,3 +1,4 @@ +import { IPaginationTransformer } from '@route/.'; import { APISchema, TemplateEngine } from '@vulcan/core'; import { @@ -21,6 +22,7 @@ type APIRouteBuilderOption = { export class RouteGenerator { private reqValidator: IRequestValidator; private reqTransformer: IRequestTransformer; + private paginationTransformer: IPaginationTransformer; private templateEngine: TemplateEngine; private apiOptions: APIRouteBuilderOption = { [APIProviderType.RESTFUL]: RestfulRoute, @@ -30,14 +32,17 @@ export class RouteGenerator { constructor({ reqValidator, reqTransformer, + paginationTransformer, templateEngine, }: { reqValidator: IRequestValidator; reqTransformer: IRequestTransformer; + paginationTransformer: IPaginationTransformer; templateEngine: TemplateEngine; }) { this.reqValidator = reqValidator; this.reqTransformer = reqTransformer; + this.paginationTransformer = paginationTransformer; this.templateEngine = templateEngine; } @@ -49,6 +54,7 @@ export class RouteGenerator { apiSchema, reqTransformer: this.reqTransformer, reqValidator: this.reqValidator, + paginationTransformer: this.paginationTransformer, templateEngine: this.templateEngine, }); } diff --git a/packages/serve/test/app.spec.ts b/packages/serve/test/app.spec.ts index c407a1aa..f895f189 100644 --- a/packages/serve/test/app.spec.ts +++ b/packages/serve/test/app.spec.ts @@ -21,6 +21,7 @@ import { RequestTransformer, RequestValidator, } from '@route/.'; +import { PaginationTransformer } from '@route/.'; describe('Test vulcan server to call restful APIs', () => { let server: VulcanServer; @@ -175,11 +176,13 @@ describe('Test vulcan server to call restful APIs', () => { beforeAll(async () => { const reqTransformer = new RequestTransformer(); const reqValidator = new RequestValidator(); + const paginationTransformer = new PaginationTransformer(); stubTemplateEngine = sinon.stubInterface(); const generator = new RouteGenerator({ reqTransformer, reqValidator, + paginationTransformer, templateEngine: stubTemplateEngine, }); const routes = await generator.multiGenerate( @@ -237,7 +240,7 @@ describe('Test vulcan server to call restful APIs', () => { const response = await reqOperation; // Assert - expect(response.body).toEqual(expected); + expect(response.body.reqParams).toEqual(expected); } ); }); diff --git a/packages/serve/test/data-query/builder/group-by-clause.spec.ts b/packages/serve/test/data-query/builder/group-by-clause.spec.ts index a909d950..08d70ba8 100644 --- a/packages/serve/test/data-query/builder/group-by-clause.spec.ts +++ b/packages/serve/test/data-query/builder/group-by-clause.spec.ts @@ -1,7 +1,15 @@ -import { GroupByClauseOperations, DataQueryBuilder } from '@data-query/.'; +import * as sinon from 'ts-sinon'; import faker from '@faker-js/faker'; +import { GroupByClauseOperations, DataQueryBuilder } from '@data-query/.'; +import { IDataSource } from '@data-source/.'; describe('Test data query builder > group by clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); + it.each([ { columns: [faker.database.column()], @@ -18,13 +26,16 @@ describe('Test data query builder > group by clause', () => { // Act let builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); columns.map((column) => { builder = builder.groupBy(column); }); // Assert - expect(builder.operations.groupBy).toEqual(expected); + expect(JSON.stringify(builder.operations.groupBy)).toEqual( + JSON.stringify(expected) + ); } ); @@ -40,11 +51,14 @@ describe('Test data query builder > group by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder.groupBy(first, second, third); // Assert - expect(builder.operations.groupBy).toEqual(expected); + expect(JSON.stringify(builder.operations.groupBy)).toEqual( + JSON.stringify(expected) + ); } ); }); diff --git a/packages/serve/test/data-query/builder/having-clause.spec.ts b/packages/serve/test/data-query/builder/having-clause.spec.ts index 64313091..030d3a94 100644 --- a/packages/serve/test/data-query/builder/having-clause.spec.ts +++ b/packages/serve/test/data-query/builder/having-clause.spec.ts @@ -1,3 +1,4 @@ +import * as sinon from 'ts-sinon'; import faker from '@faker-js/faker'; import { DataQueryBuilder, @@ -10,6 +11,7 @@ import { HavingClauseOperation, HavingPredicateInput, } from '@data-query/.'; +import { IDataSource } from '@data-source/.'; const normalized = (column: string | SelectedColumn) => { if (typeof column === 'string') return { name: column }; @@ -17,6 +19,12 @@ const normalized = (column: string | SelectedColumn) => { }; describe('Test data query builder > having clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); + it.each([ { having: { @@ -27,7 +35,10 @@ describe('Test data query builder > having clause', () => { and: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select * from products' }), + value: new DataQueryBuilder({ + statement: 'select * from products', + dataSource: sinon.stubInterface(), + }), }, }, { @@ -38,7 +49,10 @@ describe('Test data query builder > having clause', () => { aggregateType: AggregateFuncType.AVG, } as SelectedColumn, operator: '=', - value: new DataQueryBuilder({ statement: 'select avg(*) from users' }), + value: new DataQueryBuilder({ + statement: 'select avg(*) from users', + dataSource: sinon.stubInterface(), + }), }, and: { column: faker.database.column(), @@ -88,13 +102,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (having) builder.having(having.column, having.operator, having.value); if (and) builder.andHaving(and.column, and.operator, and.value); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -109,7 +124,10 @@ describe('Test data query builder > having clause', () => { or: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select * from products' }), + value: new DataQueryBuilder({ + statement: 'select * from products', + dataSource: sinon.stubInterface(), + }), }, }, { @@ -120,7 +138,10 @@ describe('Test data query builder > having clause', () => { aggregateType: AggregateFuncType.AVG, } as SelectedColumn, operator: '=', - value: new DataQueryBuilder({ statement: 'select avg(*) from users' }), + value: new DataQueryBuilder({ + statement: 'select avg(*) from users', + dataSource: sinon.stubInterface(), + }), }, or: { column: faker.database.column(), @@ -170,13 +191,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (having) builder.having(having.column, having.operator, having.value); if (or) builder.orHaving(or.column, or.operator, or.value); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -281,13 +303,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (havingIn) builder.havingIn(havingIn.column, havingIn.values); if (and) builder.andHavingIn(and.column, and.values); if (andNot) builder.andHavingNotIn(andNot.column, andNot.values); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -393,13 +416,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notIn) builder.havingNotIn(notIn.column, notIn.values); if (or) builder.orHavingIn(or.column, or.values); if (orNot) builder.orHavingNotIn(orNot.column, orNot.values); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -487,6 +511,7 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (between) builder.havingBetween(between.column, between.min, between.max); @@ -494,8 +519,8 @@ describe('Test data query builder > having clause', () => { if (andNot) builder.andHavingNotBetween(andNot.column, andNot.min, andNot.max); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -584,6 +609,7 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notBetween) builder.havingNotBetween( @@ -594,8 +620,8 @@ describe('Test data query builder > having clause', () => { if (or) builder.orHavingBetween(or.column, or.min, or.max); if (orNot) builder.orHavingNotBetween(orNot.column, orNot.min, orNot.max); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -653,13 +679,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (isNull) builder.havingNull(isNull.column); if (and) builder.andHavingNull(and.column); if (andNot) builder.andHavingNotNull(andNot.column); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -718,13 +745,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notNull) builder.havingNotNull(notNull.column); if (or) builder.orHavingNull(or.column); if (orNot) builder.orHavingNotNull(orNot.column); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -734,18 +762,21 @@ describe('Test data query builder > having clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, and: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, andNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -754,18 +785,21 @@ describe('Test data query builder > having clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, and: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, andNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -785,13 +819,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (exists) builder.havingExists(exists); if (and) builder.andHavingExists(and); if (andNot) builder.andHavingNotExists(andNot); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); @@ -801,18 +836,21 @@ describe('Test data query builder > having clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, or: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, orNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -821,18 +859,21 @@ describe('Test data query builder > having clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, or: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, orNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -853,13 +894,14 @@ describe('Test data query builder > having clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (exists) builder.havingNotExists(exists); if (or) builder.orHavingExists(or); if (orNot) builder.orHavingNotExists(orNot); // Asset - expect(builder.operations.having).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.having)).toEqual( + JSON.stringify(expected) ); } ); diff --git a/packages/serve/test/data-query/builder/join-clause.spec.ts b/packages/serve/test/data-query/builder/join-clause.spec.ts index 42e9dd5d..ebc6eac1 100644 --- a/packages/serve/test/data-query/builder/join-clause.spec.ts +++ b/packages/serve/test/data-query/builder/join-clause.spec.ts @@ -1,3 +1,4 @@ +import * as sinon from 'ts-sinon'; import { BetweenPredicateInput, ComparisonPredicate, @@ -12,10 +13,18 @@ import { LogicalOperator, NullPredicateInput, } from '@data-query/.'; +import { IDataSource } from '@data-source/.'; describe('Test data query builder > join clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); + const joinBuilder = new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }); const alias = 'products'; const joinOnClauseOperations: Array = [ @@ -112,6 +121,7 @@ describe('Test data query builder > join clause', () => { // Act const queryBuilder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); const joinCallMapper = { [JoinCommandType.INNER_JOIN]: (builder: IDataQueryBuilder) => @@ -124,8 +134,11 @@ describe('Test data query builder > join clause', () => { builder.fullJoin(joinParameters.joinBuilder, joinParameters.clause), }; joinCallMapper[command](queryBuilder); + // Assert - expect(queryBuilder.operations.join[0]).toEqual(expected); + expect(JSON.stringify(queryBuilder.operations.join[0])).toEqual( + JSON.stringify(expected) + ); } ); }); diff --git a/packages/serve/test/data-query/builder/limit-offset-clause.spec.ts b/packages/serve/test/data-query/builder/limit-offset-clause.spec.ts index 61a90580..c86b6641 100644 --- a/packages/serve/test/data-query/builder/limit-offset-clause.spec.ts +++ b/packages/serve/test/data-query/builder/limit-offset-clause.spec.ts @@ -1,7 +1,14 @@ -import { DataQueryBuilder } from '@data-query/.'; +import * as sinon from 'ts-sinon'; import faker from '@faker-js/faker'; +import { DataQueryBuilder } from '@data-query/.'; +import { IDataSource } from '@data-source/.'; describe('Test data query builder > limit-offset by clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); it.each([ { limit: faker.datatype.number({ max: 10 }), @@ -23,6 +30,7 @@ describe('Test data query builder > limit-offset by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder.limit(limit).offset(offset); @@ -65,6 +73,7 @@ describe('Test data query builder > limit-offset by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder .limit(first.limit) @@ -111,6 +120,7 @@ describe('Test data query builder > limit-offset by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder .limit(first.limit) diff --git a/packages/serve/test/data-query/builder/order-by-clause.spec.ts b/packages/serve/test/data-query/builder/order-by-clause.spec.ts index d915f97f..8a35ce2b 100644 --- a/packages/serve/test/data-query/builder/order-by-clause.spec.ts +++ b/packages/serve/test/data-query/builder/order-by-clause.spec.ts @@ -1,11 +1,18 @@ +import * as sinon from 'ts-sinon'; +import faker from '@faker-js/faker'; import { DataQueryBuilder, Direction, OrderByClauseOperation, } from '@data-query/.'; -import faker from '@faker-js/faker'; +import { IDataSource } from '@data-source/.'; describe('Test data query builder > order by clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); it.each([ { column: faker.database.column(), @@ -29,11 +36,14 @@ describe('Test data query builder > order by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder.orderBy(column, direction); // Assert - expect(builder.operations.orderBy).toEqual(expected); + expect(JSON.stringify(builder.operations.orderBy)).toEqual( + JSON.stringify(expected) + ); } ); @@ -67,13 +77,16 @@ describe('Test data query builder > order by clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); builder .orderBy(first.column, first.direction) .orderBy(second.column, second.direction); // Assert - expect(builder.operations.orderBy).toEqual(expected); + expect(JSON.stringify(builder.operations.orderBy)).toEqual( + JSON.stringify(expected) + ); } ); }); diff --git a/packages/serve/test/data-query/builder/select-clause.spec.ts b/packages/serve/test/data-query/builder/select-clause.spec.ts index e9d9f552..0dbe6f80 100644 --- a/packages/serve/test/data-query/builder/select-clause.spec.ts +++ b/packages/serve/test/data-query/builder/select-clause.spec.ts @@ -1,3 +1,4 @@ +import * as sinon from 'ts-sinon'; import faker from '@faker-js/faker'; import { AggregateFuncType, @@ -8,6 +9,7 @@ import { SelectedColumn, } from '@data-query/.'; import { find, isEmpty } from 'lodash'; +import { IDataSource } from '@data-source/.'; // Use to generate select record expected results const generateSelectRecords = ( @@ -36,6 +38,10 @@ const generateSelectRecords = ( }; describe('Test data query builder > select clause', () => { + let stubDataSource: sinon.StubbedInstance; + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); it.each([ ['*', '*', '*'], [undefined, '*', '*'], @@ -76,12 +82,15 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); columns.map((column) => { builder = column ? builder.select(column) : builder.select(); }); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -140,6 +149,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -150,7 +160,9 @@ describe('Test data query builder > select clause', () => { : builder.column(); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -209,6 +221,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -219,7 +232,9 @@ describe('Test data query builder > select clause', () => { : builder.first(); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); expect(builder.operations.limit).toEqual(1); } ); @@ -282,6 +297,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -290,7 +306,9 @@ describe('Test data query builder > select clause', () => { builder = countParam ? builder.count(countParam) : builder.count(); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -352,6 +370,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -359,7 +378,9 @@ describe('Test data query builder > select clause', () => { : builder.select(); builder.max(maxParam); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -421,6 +442,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -428,7 +450,9 @@ describe('Test data query builder > select clause', () => { : builder.select(); builder.min(minParam); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -490,6 +514,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -497,7 +522,9 @@ describe('Test data query builder > select clause', () => { : builder.select(); builder.avg(avgParam); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); @@ -559,6 +586,7 @@ describe('Test data query builder > select clause', () => { // Act let builder = new DataQueryBuilder({ statement, + dataSource: stubDataSource, }); builder = !isEmpty(selectParam) @@ -566,7 +594,9 @@ describe('Test data query builder > select clause', () => { : builder.select(); builder.sum(sumParam); // Assert - expect(builder.operations.select).toEqual(expected); + expect(JSON.stringify(builder.operations.select)).toEqual( + JSON.stringify(expected) + ); } ); }); diff --git a/packages/serve/test/data-query/builder/where-clause.spec.ts b/packages/serve/test/data-query/builder/where-clause.spec.ts index dfab1140..e4e4cf74 100644 --- a/packages/serve/test/data-query/builder/where-clause.spec.ts +++ b/packages/serve/test/data-query/builder/where-clause.spec.ts @@ -1,3 +1,4 @@ +import * as sinon from 'ts-sinon'; import faker from '@faker-js/faker'; import { DataQueryBuilder, @@ -8,8 +9,15 @@ import { AliasDataQueryBuilder, IDataQueryBuilder, } from '@data-query/.'; +import { IDataSource } from '@data-source/.'; describe('Test data query builder > where clause', () => { + let stubDataSource: sinon.StubbedInstance; + + beforeEach(() => { + stubDataSource = sinon.stubInterface(); + }); + it.each([ { where: { @@ -20,7 +28,10 @@ describe('Test data query builder > where clause', () => { and: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select * from products' }), + value: new DataQueryBuilder({ + statement: 'select * from products', + dataSource: sinon.stubInterface(), + }), }, andNot: { column: faker.database.column(), @@ -32,7 +43,10 @@ describe('Test data query builder > where clause', () => { where: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select avg(*) from users' }), + value: new DataQueryBuilder({ + statement: 'select avg(*) from users', + dataSource: sinon.stubInterface(), + }), }, and: { column: faker.database.column(), @@ -60,14 +74,15 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (where) builder.where(where.column, where.operator, where.value); if (and) builder.andWhere(and.column, and.operator, and.value); if (andNot) builder.andWhereNot(andNot.column, andNot.operator, andNot.value); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -82,7 +97,10 @@ describe('Test data query builder > where clause', () => { or: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select * from products' }), + value: new DataQueryBuilder({ + statement: 'select * from products', + dataSource: sinon.stubInterface(), + }), }, orNot: { column: faker.database.column(), @@ -94,7 +112,10 @@ describe('Test data query builder > where clause', () => { whereNot: { column: faker.database.column(), operator: '=', - value: new DataQueryBuilder({ statement: 'select avg(*) from users' }), + value: new DataQueryBuilder({ + statement: 'select avg(*) from users', + dataSource: sinon.stubInterface(), + }), }, or: { column: faker.database.column(), @@ -123,14 +144,15 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (whereNot) builder.whereNot(whereNot.column, whereNot.operator, whereNot.value); if (or) builder.orWhere(or.column, or.operator, or.value); if (orNot) builder.orWhereNot(orNot.column, orNot.operator, orNot.value); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -167,6 +189,7 @@ describe('Test data query builder > where clause', () => { column: faker.database.column(), values: new DataQueryBuilder({ statement: 'select type from products', + dataSource: sinon.stubInterface(), }), }, and: { @@ -179,7 +202,10 @@ describe('Test data query builder > where clause', () => { }, andNot: { column: faker.database.column(), - values: new DataQueryBuilder({ statement: 'select age from users' }), + values: new DataQueryBuilder({ + statement: 'select age from users', + dataSource: sinon.stubInterface(), + }), }, }, ])( @@ -197,13 +223,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (whereIn) builder.whereIn(whereIn.column, whereIn.values); if (and) builder.andWhereIn(and.column, and.values); if (andNot) builder.andWhereNotIn(andNot.column, andNot.values); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -240,6 +267,7 @@ describe('Test data query builder > where clause', () => { column: faker.database.column(), values: new DataQueryBuilder({ statement: 'select type from products', + dataSource: sinon.stubInterface(), }), }, or: { @@ -252,7 +280,10 @@ describe('Test data query builder > where clause', () => { }, orNot: { column: faker.database.column(), - values: new DataQueryBuilder({ statement: 'select age from users' }), + values: new DataQueryBuilder({ + statement: 'select age from users', + dataSource: sinon.stubInterface(), + }), }, }, ])( @@ -271,13 +302,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notIn) builder.whereNotIn(notIn.column, notIn.values); if (or) builder.orWhereIn(or.column, or.values); if (orNot) builder.orWhereNotIn(orNot.column, orNot.values); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -332,6 +364,7 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (between) builder.whereBetween(between.column, between.min, between.max); @@ -339,8 +372,8 @@ describe('Test data query builder > where clause', () => { if (andNot) builder.andWhereNotBetween(andNot.column, andNot.min, andNot.max); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -396,6 +429,7 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notBetween) builder.whereNotBetween( @@ -406,8 +440,8 @@ describe('Test data query builder > where clause', () => { if (or) builder.orWhereBetween(or.column, or.min, or.max); if (orNot) builder.orWhereNotBetween(orNot.column, orNot.min, orNot.max); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -450,13 +484,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (isNull) builder.whereNull(isNull.column); if (and) builder.andWhereNull(and.column); if (andNot) builder.andWhereNotNull(andNot.column); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -500,13 +535,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notNull) builder.whereNotNull(notNull.column); if (or) builder.orWhereNull(or.column); if (orNot) builder.orWhereNotNull(orNot.column); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -544,13 +580,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (like) builder.whereLike(like.column, like.searchValue); if (and) builder.andWhereLike(and.column, and.searchValue); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -588,13 +625,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (like) builder.whereLike(like.column, like.searchValue); if (or) builder.orWhereLike(or.column, or.searchValue); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -604,18 +642,21 @@ describe('Test data query builder > where clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, and: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, andNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -624,6 +665,7 @@ describe('Test data query builder > where clause', () => { exists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, @@ -631,6 +673,7 @@ describe('Test data query builder > where clause', () => { and: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, @@ -638,6 +681,7 @@ describe('Test data query builder > where clause', () => { andNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -657,13 +701,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (exists) builder.whereExists(exists); if (and) builder.andWhereExists(and); if (andNot) builder.andWhereNotExists(andNot); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -673,18 +718,21 @@ describe('Test data query builder > where clause', () => { notExists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, or: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, orNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -693,18 +741,21 @@ describe('Test data query builder > where clause', () => { notExists: { builder: new DataQueryBuilder({ statement: 'select * from products', + dataSource: sinon.stubInterface(), }), as: 'products', } as AliasDataQueryBuilder, or: { builder: new DataQueryBuilder({ statement: 'select * from users', + dataSource: sinon.stubInterface(), }), as: 'users', } as AliasDataQueryBuilder, orNot: { builder: new DataQueryBuilder({ statement: 'select * from orders', + dataSource: sinon.stubInterface(), }), as: 'orders', } as AliasDataQueryBuilder, @@ -725,13 +776,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notExists) builder.whereNotExists(notExists); if (or) builder.orWhereExists(or); if (orNot) builder.orWhereNotExists(orNot); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -758,7 +810,10 @@ describe('Test data query builder > where clause', () => { .whereNot( 'tags', '>', - new DataQueryBuilder({ statement: 'select count(*) from tags' }) + new DataQueryBuilder({ + statement: 'select count(*) from tags', + dataSource: sinon.stubInterface(), + }) ) .orWhereNotBetween('price', 1, 1000); }, @@ -777,11 +832,20 @@ describe('Test data query builder > where clause', () => { 'Should record successfully when call whereWrapped(...).andWhereWrapped(...).andWhereNotWrapped(...)', async ({ wrapped, and, andNot }) => { // Arrange - const wrappedBuilder = new DataQueryBuilder({ statement: '' }); + const wrappedBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); wrapped(wrappedBuilder); - const andBuilder = new DataQueryBuilder({ statement: '' }); + const andBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); and(andBuilder); - const andNotBuilder = new DataQueryBuilder({ statement: '' }); + const andNotBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); andNot(andNotBuilder); const expected: Array = [ @@ -801,13 +865,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (wrapped) builder.whereWrapped(wrapped); if (and) builder.andWhereWrapped(and); if (andNot) builder.andWhereNotWrapped(andNot); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); @@ -834,7 +899,10 @@ describe('Test data query builder > where clause', () => { .whereNot( 'tags', '>', - new DataQueryBuilder({ statement: 'select count(*) from tags' }) + new DataQueryBuilder({ + statement: 'select count(*) from tags', + dataSource: sinon.stubInterface(), + }) ) .orWhereNotBetween('price', 1, 1000); }, @@ -853,11 +921,20 @@ describe('Test data query builder > where clause', () => { 'Should record successfully when call whereNotWrapped(...).orWhereWrapped(...).orWhereNotWrapped(...)', async ({ notWrapped, or, orNot }) => { // Arrange - const notWrappedBuilder = new DataQueryBuilder({ statement: '' }); + const notWrappedBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); notWrapped(notWrappedBuilder); - const orBuilder = new DataQueryBuilder({ statement: '' }); + const orBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); or(orBuilder); - const orNotBuilder = new DataQueryBuilder({ statement: '' }); + const orNotBuilder = new DataQueryBuilder({ + statement: '', + dataSource: stubDataSource, + }); orNot(orNotBuilder); const expected: Array = [ @@ -878,13 +955,14 @@ describe('Test data query builder > where clause', () => { // Act const builder = new DataQueryBuilder({ statement: 'select * from orders', + dataSource: stubDataSource, }); if (notWrapped) builder.whereNotWrapped(notWrapped); if (or) builder.orWhereWrapped(or); if (orNot) builder.orWhereNotWrapped(orNot); // Asset - expect(builder.operations.where).toEqual( - expect.arrayContaining(expected) + expect(JSON.stringify(builder.operations.where)).toEqual( + JSON.stringify(expected) ); } ); diff --git a/packages/serve/test/data-query/joinOnClause.spec.ts b/packages/serve/test/data-query/joinOnClause.spec.ts index 7813f119..ccc6e0e9 100644 --- a/packages/serve/test/data-query/joinOnClause.spec.ts +++ b/packages/serve/test/data-query/joinOnClause.spec.ts @@ -4,9 +4,7 @@ import { JoinOnClauseOperation, JoinOnOperatorInput, LogicalOperator, - BetweenPredicateInput, ComparisonPredicate, - InPredicateInput, } from '@data-query/.'; describe('Test join on clause > on operations', () => { @@ -59,7 +57,9 @@ describe('Test join on clause > on operations', () => { const clause = new JoinOnClause(); clause.on(leftColumn, operator, rightColumn); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual( + JSON.stringify(expected) + ); } ); @@ -86,7 +86,7 @@ describe('Test join on clause > on operations', () => { ); it('Should record successfully when call join on(...).andOn(...).OrOn(...)', async () => { // Arrange - const fakeInputParams = [ + const fakeInputParams: Array = [ { leftColumn: `table1.${faker.database.column()}`, operator: '=', @@ -103,14 +103,14 @@ describe('Test join on clause > on operations', () => { rightColumn: `table2.${faker.database.column()}`, }, ]; - const expected: Array = fakeInputParams.map( - (params) => { - return { - command: null, - data: params as JoinOnOperatorInput, - }; - } - ); + const expected: Array = [ + { command: null, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: null, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: null, data: fakeInputParams[2] }, + ]; + // Act const clause = new JoinOnClause(); clause @@ -130,7 +130,7 @@ describe('Test join on clause > on operations', () => { fakeInputParams[2].rightColumn ); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); }); @@ -173,14 +173,14 @@ describe('Test join on clause > between operations', () => { }); it('Should record successfully when call join onBetween(...).andBetween(...).OrBetween(...)', async () => { // Arrange - const expected: Array = fakeInputParams.map( - (params) => { - return { - command: ComparisonPredicate.BETWEEN, - data: params as BetweenPredicateInput, - }; - } - ); + const expected: Array = [ + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[2] }, + ]; + // Act const clause = new JoinOnClause(); clause @@ -200,22 +200,21 @@ describe('Test join on clause > between operations', () => { fakeInputParams[2].max ); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); it('Should record successfully when call join onNotBetween(...).andNotBetween(...).OrNotBetween(...)', async () => { // Arrange - const expected: Array = fakeInputParams.reduce( - (operations, currentParams) => { - operations.push({ command: LogicalOperator.NOT }); - operations.push({ - command: ComparisonPredicate.BETWEEN, - data: currentParams, - }); - return operations; - }, - [] as Array - ); + const expected: Array = [ + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.BETWEEN, data: fakeInputParams[2] }, + ]; // Act const clause = new JoinOnClause(); clause @@ -235,7 +234,7 @@ describe('Test join on clause > between operations', () => { fakeInputParams[2].max ); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); }); @@ -256,14 +255,14 @@ describe('Test join on clause > in operations', () => { ]; it('Should record successfully when call join onIn(...).andIn(...).OrIn(...)', async () => { // Arrange - const expected: Array = fakeInputParams.map( - (params) => { - return { - command: ComparisonPredicate.IN, - data: params as InPredicateInput, - }; - } - ); + const expected: Array = [ + { command: ComparisonPredicate.IN, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: ComparisonPredicate.IN, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: ComparisonPredicate.IN, data: fakeInputParams[2] }, + ]; + // Act const clause = new JoinOnClause(); clause @@ -271,23 +270,22 @@ describe('Test join on clause > in operations', () => { .andOnIn(fakeInputParams[1].column, fakeInputParams[1].values) .orOnIn(fakeInputParams[2].column, fakeInputParams[2].values); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); it('Should record successfully when call join onNotIn(...).andNotIn(...).OrNotIn(...)', async () => { // Arrange + const expected: Array = [ + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IN, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IN, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IN, data: fakeInputParams[2] }, + ]; - const expected: Array = fakeInputParams.reduce( - (operations, currentParams) => { - operations.push({ command: LogicalOperator.NOT }); - operations.push({ - command: ComparisonPredicate.IN, - data: currentParams, - }); - return operations; - }, - [] as Array - ); // Act const clause = new JoinOnClause(); clause @@ -295,7 +293,7 @@ describe('Test join on clause > in operations', () => { .andOnNotIn(fakeInputParams[1].column, fakeInputParams[1].values) .orOnNotIn(fakeInputParams[2].column, fakeInputParams[2].values); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); }); @@ -313,14 +311,13 @@ describe('Test join on clause > null operations', () => { ]; it('Should record successfully when call join onNull(...).andNull(...).OrNull(...)', async () => { // Arrange - const expected: Array = fakeInputParams.map( - (params) => { - return { - command: ComparisonPredicate.IS_NULL, - data: params as BetweenPredicateInput, - }; - } - ); + const expected: Array = [ + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[2] }, + ]; // Act const clause = new JoinOnClause(); clause @@ -328,22 +325,21 @@ describe('Test join on clause > null operations', () => { .andOnNull(fakeInputParams[1].column) .orOnNull(fakeInputParams[2].column); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); - it('Should record successfully when call join onNotBetween(...).andNotBetween(...).OrNotBetween(...)', async () => { + it('Should record successfully when call join onNotNull(...).andNotNull(...).OrNotNull(...)', async () => { // Arrange - const expected: Array = fakeInputParams.reduce( - (operations, currentParams) => { - operations.push({ command: LogicalOperator.NOT }); - operations.push({ - command: ComparisonPredicate.IS_NULL, - data: currentParams, - }); - return operations; - }, - [] as Array - ); + const expected: Array = [ + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[0] }, + { command: LogicalOperator.AND }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[1] }, + { command: LogicalOperator.OR }, + { command: LogicalOperator.NOT }, + { command: ComparisonPredicate.IS_NULL, data: fakeInputParams[2] }, + ]; // Act const clause = new JoinOnClause(); clause @@ -351,6 +347,6 @@ describe('Test join on clause > null operations', () => { .andOnNotNull(fakeInputParams[1].column) .orOnNotNull(fakeInputParams[2].column); // Asset - expect(clause.operations).toEqual(expect.arrayContaining(expected)); + expect(JSON.stringify(clause.operations)).toEqual(JSON.stringify(expected)); }); }); diff --git a/packages/serve/test/route/routeGenerator.spec.ts b/packages/serve/test/route/routeGenerator.spec.ts index d79ca359..bcc8a125 100644 --- a/packages/serve/test/route/routeGenerator.spec.ts +++ b/packages/serve/test/route/routeGenerator.spec.ts @@ -9,10 +9,12 @@ import { RestfulRoute, RouteGenerator, } from '@route/.'; +import { IPaginationTransformer } from '@route/.'; describe('Test route generator ', () => { let stubReqTransformer: sinon.StubbedInstance; let stubReqValidator: sinon.StubbedInstance; + let stubPaginationTransformer: sinon.StubbedInstance; let stubTemplateEngine: sinon.StubbedInstance; const fakeSchemas: Array = Array( faker.datatype.number({ min: 2, max: 4 }) @@ -21,6 +23,7 @@ describe('Test route generator ', () => { beforeEach(() => { stubReqTransformer = sinon.stubInterface(); stubReqValidator = sinon.stubInterface(); + stubPaginationTransformer = sinon.stubInterface(); stubTemplateEngine = sinon.stubInterface(); }); @@ -32,11 +35,13 @@ describe('Test route generator ', () => { apiSchema, reqTransformer: stubReqTransformer, reqValidator: stubReqValidator, + paginationTransformer: stubPaginationTransformer, templateEngine: stubTemplateEngine, }); const routeGenerator = new RouteGenerator({ reqTransformer: stubReqTransformer, reqValidator: stubReqValidator, + paginationTransformer: stubPaginationTransformer, templateEngine: stubTemplateEngine, }); @@ -62,11 +67,13 @@ describe('Test route generator ', () => { apiSchema, reqTransformer: stubReqTransformer, reqValidator: stubReqValidator, + paginationTransformer: stubPaginationTransformer, templateEngine: stubTemplateEngine, }); const routeGenerator = new RouteGenerator({ reqTransformer: stubReqTransformer, reqValidator: stubReqValidator, + paginationTransformer: stubPaginationTransformer, templateEngine: stubTemplateEngine, }); diff --git a/packages/serve/tsconfig.json b/packages/serve/tsconfig.json index bf2689e7..76938c8c 100644 --- a/packages/serve/tsconfig.json +++ b/packages/serve/tsconfig.json @@ -14,6 +14,8 @@ "@vulcan/core": ["packages/core/src/index.ts"], "@route/*": ["packages/serve/src/lib/route/*"], "@data-query/*": ["packages/serve/src/lib/data-query/*"], + "@data-source/*": ["packages/serve/src/lib/data-source/*"], + "@pagination/*": ["packages/serve/src/lib/pagination/*"], "@app": ["packages/serve/src/lib/app.ts"] } }, diff --git a/packages/serve/tsconfig.lib.json b/packages/serve/tsconfig.lib.json index f847b219..1925baa1 100644 --- a/packages/serve/tsconfig.lib.json +++ b/packages/serve/tsconfig.lib.json @@ -5,6 +5,6 @@ "declaration": true, "types": [] }, - "include": ["**/*.ts", "../../types/*.d.ts", "src/lib/data-query/builder"], + "include": ["**/*.ts", "../../types/*.d.ts"], "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] }