Skip to content

Commit

Permalink
fix: improved audit tests
Browse files Browse the repository at this point in the history
Also perform some minor corrections.
  • Loading branch information
Josuto committed Oct 4, 2023
1 parent 529487b commit 692ce9f
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 145 deletions.
16 changes: 8 additions & 8 deletions src/mongoose.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>

async save<S extends T>(
entity: S | PartialEntityWithIdAndOptionalDiscriminatorKey<S>,
userId?: string | number,
userId?: string,
): Promise<S> {
if (!entity)
throw new IllegalArgumentException('The given entity must be valid');
Expand Down Expand Up @@ -123,11 +123,11 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>

private async insert<S extends T>(
entity: S,
userId?: string | number,
userId?: string,
): Promise<HydratedDocument<S>> {
try {
this.setDiscriminatorKeyOn(entity);
const document = this.assignUserId(entity, userId);
const document = this.createDocumentAndSetUserId(entity, userId);
return (await document.save()) as HydratedDocument<S>;
} catch (error) {
if (error.message.includes('duplicate key error')) {
Expand All @@ -151,17 +151,17 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
}
}

private assignUserId<S extends T>(entity: S, userId?: string | number) {
const tempEntity = new this.entityModel(entity);
private createDocumentAndSetUserId<S extends T>(entity: S, userId?: string) {
const document = new this.entityModel(entity);
if (isAuditable(entity) && userId) {
tempEntity.$locals.userId = userId;
document.$locals.userId = userId;
}
return tempEntity;
return document;
}

private async update<S extends T>(
entity: PartialEntityWithId<S>,
userId?: string | number,
userId?: string,
): Promise<HydratedDocument<S> | null> {
const document = await this.entityModel.findById<HydratedDocument<S>>(
entity.id,
Expand Down
26 changes: 25 additions & 1 deletion src/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,35 @@ import { Optional } from 'typescript-optional';
export type PartialEntityWithId<T> = { id: string } & Partial<T>;

export interface Repository<T extends Entity> {
/**
* Find an entity by ID.
* @param id the ID of the entity.
* @returns an Optional specifying the entity or null.
* */
findById: <S extends T>(id: string) => Promise<Optional<S>>;

/**
* Find all entities.
* @param filters some combination of entity property values to filter the result.
* @param sortBy some entity property to sort the result.
* */
findAll: <S extends T>(filters?: any, sortBy?: any) => Promise<S[]>;

/**
* Save (insert or update) an entity.
* @param entity the entity to save.
* @param userId the ID of the user executing the action.
* @returns the saved version of the entity.
*/
save: <S extends T>(
entity: S | PartialEntityWithId<S>,
userId?: string | number,
userId?: string,
) => Promise<S>;

/**
* Delete an entity by ID.
* @param id the ID of the entity.
* @returns true if the entity was deleted, false otherwise.
*/
deleteById: (id: string) => Promise<boolean>;
}
8 changes: 4 additions & 4 deletions src/util/audit.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
export interface Auditable {
createdAt?: Date;
createdBy?: string | number;
createdBy?: string;
updatedAt?: Date;
updatedBy?: string | number;
updatedBy?: string;
}

export abstract class AuditableClass implements Auditable {
readonly createdAt?: Date;
readonly createdBy?: string | number;
readonly createdBy?: string;
readonly updatedAt?: Date;
readonly updatedBy?: string | number;
readonly updatedBy?: string;

constructor(entity: Auditable) {
this.createdAt = entity.createdAt;
Expand Down
4 changes: 2 additions & 2 deletions src/util/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export const BaseSchema = new Schema(
export const AuditableSchema = extendSchema(
BaseSchema,
{
createdBy: { type: String, required: false },
updatedBy: { type: String, required: false },
createdBy: { type: String },
updatedBy: { type: String },
},
{ timestamps: true },
);
Expand Down
236 changes: 106 additions & 130 deletions test/repository/auditable.book.repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,156 +7,132 @@ import {
} from '../util/mongo-server';
import { AuditableMongooseBookRepository } from './auditable.book.repository';

describe('', () => {
describe('Given an instance of auditable book repository and a user ID', () => {
let bookRepository: Repository<AuditableBook>;
const createdBy = '1234';

beforeAll(async () => {
setupConnection();
bookRepository = new AuditableMongooseBookRepository();
});

describe('Given an instance of auditable book repository', () => {
describe('when saving an auditable book', () => {
describe('that is new', () => {
describe('and that is of supertype AuditableBook', () => {
it('then the inserted book includes the expected audit data', async () => {
const bookToInsert = new AuditableBook({
title: 'Continuous Delivery',
description:
'Reliable Software Releases Through Build, Test, and Deployment Automation',
isbn: '9780321601919',
});

const auditableBook = await bookRepository.save(
bookToInsert,
'1234',
);
expect(auditableBook.createdAt).toBeDefined();
expect(auditableBook.updatedAt).toBeDefined();
expect(auditableBook.createdBy).toBeDefined();
expect(auditableBook.updatedBy).toBeDefined();
});
describe('when creating an auditable book', () => {
describe('that is of supertype AuditableBook', () => {
it('then the inserted book includes the expected audit data', async () => {
const bookToInsert = new AuditableBook({
title: 'Continuous Delivery',
description:
'Reliable Software Releases Through Build, Test, and Deployment Automation',
isbn: '9780321601919',
});

describe('and that is of a subtype of AuditableBook', () => {
it('then the inserted book includes the expected audit data', async () => {
const bookToInsert = new AuditablePaperBook({
title: 'Implementing Domain-Driven Design',
description: 'Describes Domain-Driven Design in depth',
edition: 1,
isbn: '9780321834577',
});

const auditableBook = await bookRepository.save(
bookToInsert,
'1234',
);
expect(auditableBook.createdAt).toBeDefined();
expect(auditableBook.updatedAt).toBeDefined();
expect(auditableBook.createdBy).toBeDefined();
expect(auditableBook.updatedBy).toBeDefined();
});
});
const auditableBook = await bookRepository.save(
bookToInsert,
createdBy,
);
expect(auditableBook.createdAt).toBeDefined();
expect(auditableBook.updatedAt).toBeDefined();
expect(auditableBook.createdBy).toEqual(createdBy);
expect(auditableBook.updatedBy).toEqual(createdBy);
});
});

describe('that is not new', () => {
let storedAuditableBook: AuditableBook;

beforeEach(async () => {
const auditableBook = new AuditableBook({
title: 'The Phoenix Project',
description:
'Building and Scaling High Performing Technology Organizations',
isbn: '1942788339',
});
storedAuditableBook = await bookRepository.save(
auditableBook,
'1234',
);
describe('that is of a subtype of AuditableBook', () => {
it('then the inserted book includes the expected audit data', async () => {
const bookToInsert = new AuditablePaperBook({
title: 'Implementing Domain-Driven Design',
description: 'Describes Domain-Driven Design in depth',
edition: 1,
isbn: '9780321834577',
});

describe('and that is of supertype AuditableBook', () => {
it('then the updated book includes the expected audit data', async () => {
const bookToUpdate = {
id: storedAuditableBook.id,
description:
'A Novel About IT, DevOps, and Helping Your Business Win',
} as AuditableBook;

const auditableBook = await bookRepository.save(
bookToUpdate,
'5678',
);
expect(auditableBook.createdAt).toEqual(
storedAuditableBook.createdAt,
);
expect(auditableBook.updatedAt?.getTime()).toBeGreaterThan(
storedAuditableBook.updatedAt!.getTime(),
);
expect(auditableBook.createdBy).toEqual(
storedAuditableBook.createdBy,
);
expect(auditableBook.updatedBy).not.toEqual(
storedAuditableBook.createdBy,
);
expect(auditableBook.updatedBy).not.toEqual(
storedAuditableBook.updatedBy,
);
});
});
const auditableBook = await bookRepository.save(
bookToInsert,
createdBy,
);
expect(auditableBook.createdAt).toBeDefined();
expect(auditableBook.updatedAt).toBeDefined();
expect(auditableBook.createdBy).toEqual(createdBy);
expect(auditableBook.updatedBy).toEqual(createdBy);
});
});
});

describe('and that is of a subtype of AuditableBook', () => {
let storedAuditablePaperBook: AuditablePaperBook;

beforeEach(async () => {
const auditablePaperBook = new AuditablePaperBook({
title: 'Effective Java',
description: 'Great book on the Java programming language',
edition: 3,
isbn: '0134685997',
});

storedAuditablePaperBook = await bookRepository.save(
auditablePaperBook,
'1234',
);
});

it('then the updated book includes the expected audit data', async () => {
const bookToUpdate = {
id: storedAuditablePaperBook.id,
edition: 4,
} as AuditablePaperBook;

const auditablePaperBook = await bookRepository.save(
bookToUpdate,
'5678',
);
expect(auditablePaperBook.createdAt).toEqual(
storedAuditablePaperBook.createdAt,
);
expect(auditablePaperBook.updatedAt?.getTime()).toBeGreaterThan(
storedAuditablePaperBook.updatedAt!.getTime(),
);
expect(auditablePaperBook.createdBy).toEqual(
storedAuditablePaperBook.createdBy,
);
expect(auditablePaperBook.updatedBy).not.toEqual(
storedAuditablePaperBook.createdBy,
);
expect(auditablePaperBook.updatedBy).not.toEqual(
storedAuditablePaperBook.updatedBy,
);
});
});
describe('when updating an auditable book', () => {
let storedAuditableBook: AuditableBook;
const updatedBy = '5678';

beforeEach(async () => {
const auditableBook = new AuditableBook({
title: 'The Phoenix Project',
description:
'Building and Scaling High Performing Technology Organizations',
isbn: '1942788339',
});
storedAuditableBook = await bookRepository.save(auditableBook, createdBy);
});

afterEach(async () => {
await deleteAll('auditablebooks');
describe('that is of supertype AuditableBook', () => {
it('then the updated book includes the expected audit data', async () => {
const bookToUpdate = {
id: storedAuditableBook.id,
description:
'A Novel About IT, DevOps, and Helping Your Business Win',
} as AuditableBook;

const auditableBook = await bookRepository.save(bookToUpdate, '5678');
expect(auditableBook.createdAt).toEqual(storedAuditableBook.createdAt);
expect(auditableBook.updatedAt?.getTime()).toBeGreaterThan(
storedAuditableBook.updatedAt!.getTime(),
);
expect(auditableBook.createdBy).toEqual(createdBy);
expect(auditableBook.updatedBy).toEqual(updatedBy);
});
});

describe('that is of a subtype of AuditableBook', () => {
let storedAuditablePaperBook: AuditablePaperBook;

beforeEach(async () => {
const auditablePaperBook = new AuditablePaperBook({
title: 'Effective Java',
description: 'Great book on the Java programming language',
edition: 3,
isbn: '0134685997',
});

storedAuditablePaperBook = await bookRepository.save(
auditablePaperBook,
createdBy,
);
});

it('then the updated book includes the expected audit data', async () => {
const bookToUpdate = {
id: storedAuditablePaperBook.id,
edition: 4,
} as AuditablePaperBook;

const auditablePaperBook = await bookRepository.save(
bookToUpdate,
updatedBy,
);
expect(auditablePaperBook.createdAt).toEqual(
storedAuditablePaperBook.createdAt,
);
expect(auditablePaperBook.updatedAt?.getTime()).toBeGreaterThan(
storedAuditablePaperBook.updatedAt!.getTime(),
);
expect(auditablePaperBook.createdBy).toEqual(createdBy);
expect(auditablePaperBook.updatedBy).toEqual(updatedBy);
});
});
});

afterEach(async () => {
await deleteAll('auditablebooks');
});

afterAll(async () => {
await closeMongoConnection();
});
Expand Down

0 comments on commit 692ce9f

Please sign in to comment.