Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync Master with Dev #37

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/libs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches:
- master
pull_request:
pull_request_target:

env:
PROJECT_ID: libs
Expand Down
79 changes: 73 additions & 6 deletions ag-grid/src/__tests__/generic-service.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
import {
BaseEntity,
Connection,
Repository,
ConnectionNotFoundError,
DeleteResult,
getConnection,
QueryFailedError,
InsertResult,
QueryFailedError,
Repository,
UpdateResult,
DeleteResult,
} from 'typeorm';
import {
baseEntityRepository as _baseEntityRepository,
Expand All @@ -22,18 +23,18 @@ import {
import { getConnectionName } from '@nestjs-yalc/database/conn.helper';
import { createMock } from '@golevelup/ts-jest';
import { AgGridRepository } from '@nestjs-yalc/ag-grid/ag-grid.repository';
import { ConnectionNotFoundError } from 'typeorm';
import { FactoryProvider } from '@nestjs/common';
import {
CreateEntityError,
DeleteEntityError,
UpdateEntityError,
} from '../entity.error';
import {
NoResultsFoundError,
ConditionsTooBroadError,
NoResultsFoundError,
} from '../conditions.error';
import * as ClassHelper from '@nestjs-yalc/utils/class.helper';

jest.mock('typeorm');

describe('GenericService', () => {
Expand Down Expand Up @@ -295,7 +296,7 @@ describe('GenericService', () => {

baseEntityRepository.insert.mockResolvedValueOnce(insertResult);
baseEntityRepository.getOneAgGrid.mockResolvedValueOnce(mockedEntity);
expect(service.createEntity({})).resolves.toBe(mockedEntity);
await expect(service.createEntity({})).resolves.toBe(mockedEntity);
});

it('Should insert an entity correctly with true', async () => {
Expand Down Expand Up @@ -326,6 +327,51 @@ describe('GenericService', () => {
mockedIsClass.mockRestore();
});

it('Should upsert an entity correctly', async () => {
const mockedEntity = new BaseEntity();
const insertResult = new InsertResult();
insertResult.identifiers = [{ id: '123' }];

baseEntityRepository.upsert.mockResolvedValueOnce(insertResult);
baseEntityRepository.getOneAgGrid.mockResolvedValueOnce(mockedEntity);
await expect(
service.upsertEntity({}, { conflictPaths: ['id'] }),
).resolves.toBe(mockedEntity);
});

it('Should upsert an entity correctly with true', async () => {
const mockedEntity = new BaseEntity();
const insertResult = new InsertResult();
insertResult.identifiers = [{ id: '123' }];

baseEntityRepository.upsert.mockResolvedValueOnce(insertResult);
baseEntityRepository.getOneAgGrid.mockResolvedValueOnce(mockedEntity);
const result = await service.upsertEntity(
{},
{ conflictPaths: ['id'] },
{},
false,
);
expect(result).toBe(true);
baseEntityRepository.upsert.mockRestore();
baseEntityRepository.getOneAgGrid.mockRestore();
});

it('Should upsert an entity correctly when entity isClass', async () => {
const mockedEntity = new BaseEntity();
const insertResult = new InsertResult();
insertResult.identifiers = [{ id: '123' }];
const mockedIsClass = jest
.spyOn(ClassHelper, 'isClass')
.mockReturnValue(true);

baseEntityRepository.upsert.mockResolvedValueOnce(insertResult);
baseEntityRepository.getOneAgGrid.mockResolvedValueOnce(mockedEntity);
const result = await service.upsertEntity({}, { conflictPaths: ['id'] });
expect(result).toBe(mockedEntity);
mockedIsClass.mockRestore();
});

it('should correctly map entities from read to write', () => {
const writeRepo = new AgGridRepository();
writeRepo.target = WriteEntity;
Expand Down Expand Up @@ -424,6 +470,27 @@ describe('GenericService', () => {
expect(result).toBeTruthy();
});

it('should delete entities correctly', async () => {
const deleteResult = new DeleteResult();
deleteResult.affected = 2;

baseEntityRepository.delete.mockResolvedValueOnce(deleteResult);

const result = await service.deleteEntities({});
expect(result).toBe(2);
expect(baseEntityRepository.delete).toHaveBeenCalled();
});

it('should return 0 when delete entities did not affect any rows', async () => {
const deleteResult = new DeleteResult();

baseEntityRepository.delete.mockResolvedValueOnce(deleteResult);

const result = await service.deleteEntities({});
expect(result).toBe(0);
expect(baseEntityRepository.delete).toHaveBeenCalled();
});

it('should handle an insertion error', async () => {
baseEntityRepository.insert.mockRejectedValueOnce(
new QueryFailedError('', [], ''),
Expand Down
94 changes: 76 additions & 18 deletions ag-grid/src/generic-service.service.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
import {
ConditionsTooBroadError,
NoResultsFoundError,
} from './conditions.error';
import {
CreateEntityError,
DeleteEntityError,
EntityError,
UpdateEntityError,
} from './entity.error';
import { ConditionsTooBroadError, NoResultsFoundError } from './conditions.error';
import { CreateEntityError, DeleteEntityError, EntityError, UpdateEntityError } from './entity.error';
import { getConnectionName } from '@nestjs-yalc/database/conn.helper';
import { FactoryProvider, Injectable } from '@nestjs/common';
import { getRepositoryToken } from '@nestjs/typeorm';
import { EntityClassOrSchema } from '@nestjs/typeorm/dist/interfaces/entity-class-or-schema.type';
import {
DeepPartial,
getConnection,
ObjectLiteral,
QueryFailedError,
} from 'typeorm';
import { FindConditions } from 'typeorm';
import { FindManyOptions } from 'typeorm';
import { DeepPartial, FindConditions, FindManyOptions, getConnection, ObjectLiteral, QueryFailedError } from 'typeorm';
import { AgGridRepository } from '@nestjs-yalc/ag-grid/ag-grid.repository';
import { AgGridFindManyOptions } from '@nestjs-yalc/ag-grid/ag-grid.interface';
import { ClassType } from '@nestjs-yalc/types/globals';
import { getProviderToken } from './ag-grid.helpers';
import { ReplicationMode } from '@nestjs-yalc/database/query-builder.helper';
import { isClass } from '@nestjs-yalc/utils/class.helper';
import { getAgGridFieldMetadataList, isDstExtended } from './object.decorator';
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';

/**
*
Expand Down Expand Up @@ -336,6 +322,61 @@ export class GenericService<EntityRead, EntityWrite = EntityRead> {
);
}

/**
* Upsert an entity based in the provided data and returns it
* @param entity
* @throws CreateEntityError
*/
async upsertEntity(
input: DeepPartial<EntityRead>,
upsertOptions: UpsertOptions<EntityRead>,
findOptions?: AgGridFindManyOptions<EntityRead>,
returnEntity?: true,
): Promise<EntityRead>;
async upsertEntity(
input: DeepPartial<EntityRead>,
upsertOptions: UpsertOptions<EntityRead>,
findOptions?: AgGridFindManyOptions<EntityRead>,
returnEntity?: boolean,
): Promise<EntityRead | boolean>;
async upsertEntity(
input: DeepPartial<EntityRead>,
upsertOptions: UpsertOptions<EntityRead>,
findOptions?: AgGridFindManyOptions<EntityRead>,
returnEntity = true,
): Promise<EntityRead | boolean> {
/**
* This is needed to keep the prototype of the
* entity in order to allow the beforeInsert to be executed
*/
let entityHydrated = this.mapEntityR2W(input);
const entity = this.entityWrite;
if (isClass(entity)) {
const inputValues = entityHydrated;
entityHydrated = new entity();
Object.assign(entityHydrated, inputValues);
}

const newEntity = this.repositoryWrite.create(entityHydrated);
const { identifiers } = await this.repositoryWrite
.upsert(newEntity, upsertOptions)
.catch(validateSupportedError(CreateEntityError));
/**
* Create where condition for the identifiers
* @todo maybe conversion is needed here as well
*/
const ids = identifiers[0];
const filters = this.repository.generateFilterOnPrimaryColumn(ids);

return !returnEntity
? true
: this.repository.getOneAgGrid(
{ ...findOptions, where: { filters } },
true,
ReplicationMode.MASTER,
);
}

/**
* Updates an entity based in the provided conditions and return the updated entity
* @param conditions The conditions to update
Expand Down Expand Up @@ -416,6 +457,23 @@ export class GenericService<EntityRead, EntityWrite = EntityRead> {
return !!result.affected && result.affected > 0;
}

/**
* Deletes all entities that match the provided conditions and returns how many entities where affected
* @param conditions The conditions to delete
* @throws DeleteEntityError
*/
async deleteEntities(
conditions: FindConditions<EntityRead>,
): Promise<number> {
const mappedConditions = this.mapEntityR2W(conditions);

const result = await this.repositoryWrite
.delete(mappedConditions)
.catch(validateSupportedError(DeleteEntityError));

return result.affected || 0;
}

/**
* Makes sure that the conditions will affect a single record
* @param conditions
Expand Down
7 changes: 4 additions & 3 deletions jest/src/config/jest-def.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,10 @@ const defaultConf = (dirname: string) => {
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: dirname,
}),
moduleNameMapper: {
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: dirname, }),
'^axios$': require.resolve('axios'),
},
errorOnDeprecated: true,
};
};
Expand Down
Loading