Skip to content

Commit

Permalink
feat: add typeorm car rental write repo, refactor db master/slave config
Browse files Browse the repository at this point in the history
  • Loading branch information
haroldcohen committed Nov 15, 2023
1 parent 4b82685 commit 0523650
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 65 deletions.
41 changes: 19 additions & 22 deletions src/configuration/injection/containers/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,31 @@ import 'dotenv/config';
const useAppDataSources = (): void => {
// Most of the variables are currently not typed or parsed correctly
// See ticket https://github.com/thetribeio/megahertz/issues/15
const commandAppDataSource = new DataSource({
const AppDataSource = new DataSource({
type: "postgres",
host: process.env.TYPEORM_MASTER_DATABASE_HOST as string,
port: Number(process.env.TYPEORM_MASTER_DATABASE_PORT),
username: process.env.TYPEORM_MASTER_DATABASE_USER as string,
password: process.env.TYPEORM_MASTER_DATABASE_PASSWORD as string,
database: process.env.TYPEORM_MASTER_DATABASE_NAME as string,
synchronize: true,
logging: false,
logging: true,
entities: ['src/driven/repositories/typeorm/entities/*.{ts,js}'],
subscribers: [],
migrations: ['src/driven/repositories/typeorm/migrations/*.{ts,js}'],
replication: {
master: {
host: process.env.TYPEORM_MASTER_DATABASE_HOST as string,
port: Number(process.env.TYPEORM_MASTER_DATABASE_PORT),
username: process.env.TYPEORM_MASTER_DATABASE_USER as string,
password: process.env.TYPEORM_MASTER_DATABASE_PASSWORD as string,
database: process.env.TYPEORM_MASTER_DATABASE_NAME as string,
},
slaves: [{
host: process.env.TYPEORM_DATABASE_HOST as string,
port: Number(process.env.TYPEORM_DATABASE_PORT),
username: process.env.TYPEORM_DATABASE_USER as string,
password: process.env.TYPEORM_DATABASE_PASSWORD as string,
database: process.env.TYPEORM_DATABASE_NAME as string,
}]
},
});
const queryAppDataSource = new DataSource({
type: "postgres",
host: process.env.TYPEORM_DATABASE_HOST as string,
port: Number(process.env.TYPEORM_DATABASE_PORT),
username: process.env.TYPEORM_DATABASE_USER as string,
password: process.env.TYPEORM_DATABASE_PASSWORD as string,
database: process.env.TYPEORM_DATABASE_NAME as string,
synchronize: true,
logging: false,
entities: ['src/driven/repositories/typeorm/entities/*.{ts,js}'],
subscribers: [],
migrations: ['src/driven/repositories/typeorm/migrations/*.{ts,js}'],
});
container.register("QueryDataSource", {useValue: queryAppDataSource});
container.register("CommandDataSource", {useValue: commandAppDataSource});
container.register("DataSource", {useValue: AppDataSource});
}

export default useAppDataSources;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {container} from 'tsyringe';
import TypeORMCarRentalReadRepository from 'src/driven/repositories/typeorm/carRental/read';
import TypeORMCarRentalWriteRepository from "src/driven/repositories/typeorm/carRental/write";

/**
* Configures tsyringe to use typeORM repositories.
Expand All @@ -8,6 +9,9 @@ const useTypeORMRepositories = (): void => {
container.register("CarRentalReadRepositoryInterface", {useClass: TypeORMCarRentalReadRepository});
const carRentalReadRepository = container.resolve("CarRentalReadRepositoryInterface");
container.registerInstance("CarRentalReadRepositoryInterface", carRentalReadRepository);
container.register("CarRentalWriteRepositoryInterface", {useClass: TypeORMCarRentalWriteRepository});
const carRentalWriteRepository = container.resolve("CarRentalWriteRepositoryInterface");
container.registerInstance("CarRentalWriteRepositoryInterface", carRentalWriteRepository);
}

export default useTypeORMRepositories;
2 changes: 1 addition & 1 deletion src/driven/repositories/typeorm/carRental/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default class TypeORMCarRentalReadRepository implements CarRentalReadRepo

private readonly dataSource: DataSource;

constructor(@inject("QueryDataSource") dataSource: DataSource) {
constructor(@inject("DataSource") dataSource: DataSource) {
this.dataSource = dataSource;
}

Expand Down
29 changes: 29 additions & 0 deletions src/driven/repositories/typeorm/carRental/write.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import CarRentalWriteRepositoryInterface from "src/core/domain/carRental/interfaces/repositories/write";
import CarRentalDTO from "src/core/domain/carRental/dto";
import {TypeORMCarRental} from "src/driven/repositories/typeorm/entities";
import {DataSource, Repository} from "typeorm";

Check failure on line 4 in src/driven/repositories/typeorm/carRental/write.ts

View workflow job for this annotation

GitHub Actions / lint (20.x)

'Repository' is defined but never used
import {inject, singleton} from "tsyringe";

@singleton()
export default class TypeORMCarRentalWriteRepository implements CarRentalWriteRepositoryInterface {

private readonly dataSource: DataSource;

constructor(@inject("DataSource") dataSource: DataSource) {
this.dataSource = dataSource;
}

async create(carRentalDTO: CarRentalDTO): Promise<void> {
const repository = this.dataSource.getRepository(TypeORMCarRental);
const typeORMCarRental = new TypeORMCarRental();
typeORMCarRental.id = carRentalDTO.id;
typeORMCarRental.totalPrice = carRentalDTO.totalPrice;
typeORMCarRental.pickupDateTime = carRentalDTO.pickupDateTime;
typeORMCarRental.dropOffDateTime = carRentalDTO.dropOffDateTime;
typeORMCarRental.customer = {id: carRentalDTO.customerId};
typeORMCarRental.car = <any>{id: carRentalDTO.car.id};

await repository.save(typeORMCarRental);
}

}
97 changes: 97 additions & 0 deletions tests/integration/typeorm/carRental/command/create.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'reflect-metadata';
import {container} from "tsyringe";
import TypeORMCarRentalWriteRepository from "src/driven/repositories/typeorm/carRental/write";
import useAppDataSources from "src/configuration/injection/containers/database";
import useTypeORMRepositories from "src/configuration/injection/containers/repositories/typeorm";
import CarRentalDTO from "src/core/domain/carRental/dto";
import TypeORMCarRentalReadRepository from "src/driven/repositories/typeorm/carRental/read";
import {runDataSourceBeforeEachOps} from "tests/integration/typeorm/utils/setup";
import DateParser from "tests/utils/dateParser";
import {advanceTo} from "jest-date-mock";
import useTestingUtilities from "tests/configuration/containers/utils";
import {DataSource} from "typeorm";
import {v4} from "uuid";
import {runDataSourceAfterEachOps} from "tests/integration/typeorm/utils/tearDown";
import {
populateCarAndCarModelFromCarRentalTestCase,
populateCustomerFromCarRentalTestCase
} from "tests/integration/typeorm/carRental/utils/populateFromTestCase";
import {expectedCarRentalFromTestCase} from "tests/integration/typeorm/utils/misc";

describe.each([
{
rental: {
id: v4(),
customerId: v4(),
car: {
id: v4(),
model: {
id: v4(),
dailyRate: 100,
}
},
totalPrice: 100,
pickupDateTime: 'today',
dropOffDateTime: 'tomorrow',
}
},
{
rental: {
id: v4(),
customerId: v4(),
car: {
id: v4(),
model: {
id: v4(),
dailyRate: 100,
}
},
totalPrice: 200,
pickupDateTime: 'tomorrow',
dropOffDateTime: 'in 2 days',
}
},
])("Integration tests to create car rental in a postgres database using typeorm", (testCase) => {
let repository: TypeORMCarRentalWriteRepository;
let readRepository: TypeORMCarRentalReadRepository;
let carRentalToCreate: CarRentalDTO;
let expectedCarRental: CarRentalDTO;
let dateParser: DateParser;

beforeAll(() => {
advanceTo(Date.now());
useTestingUtilities();
useAppDataSources();
useTypeORMRepositories();
dateParser = container.resolve("DateParser");
})

beforeEach(async () => {
repository = container.resolve("CarRentalWriteRepositoryInterface");
readRepository = container.resolve("CarRentalReadRepositoryInterface");
await runDataSourceBeforeEachOps();
await populateCustomerFromCarRentalTestCase(testCase.rental);
await populateCarAndCarModelFromCarRentalTestCase(testCase.rental);
expectedCarRental = expectedCarRentalFromTestCase(
testCase.rental,
dateParser,
);
carRentalToCreate = expectedCarRental;
})

afterEach(async () => {
await runDataSourceAfterEachOps();
})

test(`Create a car rental ${testCase.rental.id} should create one car rental in the database`, async () => {
const dataSource: DataSource = container.resolve("DataSource");
const queryRunner = dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
await repository.create(carRentalToCreate);
await queryRunner.commitTransaction();
await queryRunner.release();
const retrievedCarRental = await readRepository.read(testCase.rental.id);
expect(retrievedCarRental.toDTO()).toEqual(expectedCarRental);
})
})
40 changes: 13 additions & 27 deletions tests/integration/typeorm/carRental/query/read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import TypeORMCarFactory from 'tests/integration/typeorm/seeding/factories/car';
import TypeORMCarModelFactory from 'tests/integration/typeorm/seeding/factories/carModel';
import {runDataSourceBeforeEachOps} from 'tests/integration/typeorm/utils/setup';
import {runDataSourceAfterEachOps} from 'tests/integration/typeorm/utils/tearDown';
import {
populateCarAndCarModelFromCarRentalTestCase,
populateCustomerFromCarRentalTestCase
} from "tests/integration/typeorm/carRental/utils/populateFromTestCase";
import {expectedCarRentalFromTestCase} from "tests/integration/typeorm/utils/misc";

describe.each([
{
Expand Down Expand Up @@ -48,33 +53,24 @@ describe.each([
dropOffDateTime: 'in 2 days',
}
},
])("Integration tests to read car rentals from a postgres database using typeorm", (testCase) => {
])("Integration tests to read car rental from a postgres database using typeorm", (testCase) => {
let repository: TypeORMCarRentalReadRepository;
let expectedCarRental: Partial<CarRentalDTO>;
let dateParser: DateParser;

beforeAll(async () => {
advanceTo(Date.now());
useTestingUtilities();
dateParser = container.resolve("DateParser");
useAppDataSources();
useTypeORMRepositories();
dateParser = container.resolve("DateParser");
repository = container.resolve("CarRentalReadRepositoryInterface");
})

beforeEach(async () => {
await runDataSourceBeforeEachOps();
const customer = await new TypeORMCustomerFactory().create({
id: testCase.rental.customerId,
});
const model = await new TypeORMCarModelFactory().create({
id: testCase.rental.car.model.id,
dailyRate: testCase.rental.car.model.dailyRate,
});
const car = await new TypeORMCarFactory().create({
id: testCase.rental.car.id,
model
});
const customer = await populateCustomerFromCarRentalTestCase(testCase.rental);
const car = await populateCarAndCarModelFromCarRentalTestCase(testCase.rental);
await new TypeORMCarRentalFactory().create({
id: testCase.rental.id,
totalPrice: testCase.rental.totalPrice,
Expand All @@ -83,20 +79,10 @@ describe.each([
customer,
car,
});
expectedCarRental = {
id: testCase.rental.id,
customerId: testCase.rental.customerId,
car: {
id: testCase.rental.car.id,
model: {
id: testCase.rental.car.model.id,
dailyRate: testCase.rental.car.model.dailyRate,
},
},
pickupDateTime: dateParser.parse(testCase.rental.pickupDateTime),
dropOffDateTime: dateParser.parse(testCase.rental.dropOffDateTime),
totalPrice: testCase.rental.totalPrice,
}
expectedCarRental = expectedCarRentalFromTestCase(
testCase.rental,
dateParser,
);
})

afterEach(async () => {
Expand Down
27 changes: 27 additions & 0 deletions tests/integration/typeorm/carRental/utils/populateFromTestCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {populateCar, populateCarModel, populateCustomer} from "tests/integration/typeorm/utils/populate";
import {TypeORMCar, TypeORMCarModel, TypeORMCustomer} from "src/driven/repositories/typeorm/entities";
import {CarRentalTestCaseEntry} from "tests/integration/typeorm/carRental/utils/testCase.types";

export const populateCustomerFromCarRentalTestCase = async (
testCase: CarRentalTestCaseEntry,
): Promise<TypeORMCustomer> => {
return await populateCustomer({id: testCase.customerId});
}

export const populateCarModelFromCarRentalTestCase = async (
testCase: CarRentalTestCaseEntry,
): Promise<TypeORMCarModel> => {
return await populateCarModel({
id: testCase.car.model.id,
dailyRate: testCase.car.model.dailyRate,
});
}

export const populateCarAndCarModelFromCarRentalTestCase = async (
testCase: CarRentalTestCaseEntry,
): Promise<TypeORMCar> => {
return await populateCar({
id: testCase.car.id,
model: await populateCarModelFromCarRentalTestCase(testCase),
})
}
18 changes: 18 additions & 0 deletions tests/integration/typeorm/carRental/utils/testCase.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export type CarRentalTestCaseEntry = {
id: string;
pickupDateTime: string;
dropOffDateTime: string;
customerId: string;
car: CarTestCaseEntry;
totalPrice: number;
};

export type CarModelTestCaseEntry = {
id: string;
dailyRate: number;
}

export type CarTestCaseEntry = {
id: string;
model: CarModelTestCaseEntry;
};
3 changes: 2 additions & 1 deletion tests/integration/typeorm/seeding/factories/car.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import {v4} from 'uuid';
import {FactorizedAttrs, Factory} from '@jorgebodega/typeorm-factory';
import {TypeORMCar} from 'src/driven/repositories/typeorm/entities';


export default class TypeORMCarFactory extends Factory<TypeORMCar> {
protected entity = TypeORMCar;
protected dataSource = container.resolve("CommandDataSource") as DataSource;
protected dataSource = container.resolve("DataSource") as DataSource;

protected attrs(): FactorizedAttrs<TypeORMCar> {
return {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/typeorm/seeding/factories/carModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {TypeORMCarModel} from 'src/driven/repositories/typeorm/entities';

export default class TypeORMCarModelFactory extends Factory<TypeORMCarModel> {
protected entity = TypeORMCarModel;
protected dataSource = container.resolve("CommandDataSource") as DataSource;
protected dataSource = container.resolve("DataSource") as DataSource;

protected attrs(): FactorizedAttrs<TypeORMCarModel> {
return {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/typeorm/seeding/factories/carRental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {TypeORMCar, TypeORMCarRental, TypeORMCustomer} from 'src/driven/reposito

export default class TypeORMCarRentalFactory extends Factory<TypeORMCarRental> {
protected entity = TypeORMCarRental;
protected dataSource = container.resolve("CommandDataSource") as DataSource;
protected dataSource = container.resolve("DataSource") as DataSource;

protected attrs(): FactorizedAttrs<TypeORMCarRental> {
return {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/typeorm/seeding/factories/customer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {TypeORMCustomer} from 'src/driven/repositories/typeorm/entities';

export default class TypeORMCustomerFactory extends Factory<TypeORMCustomer> {
protected entity = TypeORMCustomer;
protected dataSource = container.resolve("CommandDataSource") as DataSource;
protected dataSource = container.resolve("DataSource") as DataSource;

protected attrs(): FactorizedAttrs<TypeORMCustomer> {
return {
Expand Down
23 changes: 23 additions & 0 deletions tests/integration/typeorm/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import CarRentalDTO from "src/core/domain/carRental/dto";
import {CarRentalTestCaseEntry} from "tests/integration/typeorm/carRental/utils/testCase.types";
import DateParser from "tests/utils/dateParser";

export const expectedCarRentalFromTestCase = (
testCase: CarRentalTestCaseEntry,
dateParser: DateParser,
): CarRentalDTO => {
return {
id: testCase.id,
customerId: testCase.customerId,
car: {
id: testCase.car.id,
model: {
id: testCase.car.model.id,
dailyRate: testCase.car.model.dailyRate,
},
},
pickupDateTime: dateParser.parse(testCase.pickupDateTime),
dropOffDateTime: dateParser.parse(testCase.dropOffDateTime),
totalPrice: testCase.totalPrice,
} as CarRentalDTO;
}
Loading

0 comments on commit 0523650

Please sign in to comment.