Skip to content
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
Empty file added .github/.keep
Empty file.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/W3nV4mdD)
# Banking management

## Overview
Expand Down
35 changes: 22 additions & 13 deletions src/__tests__/real.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { describe, beforeEach, it, expect } from 'vitest';
import { TestFactory } from './helpers/TestFactory';
import type { TestFixtures } from './helpers/TestFactory';
import { describe, beforeEach, it, expect } from "vitest";
import { TestFactory } from "./helpers/TestFactory";
import type { TestFixtures } from "./helpers/TestFactory";

describe('Bank Transfer Tests', () => {
describe("Bank Transfer Tests", () => {
let fixtures: TestFixtures;

beforeEach(() => {
fixtures = TestFactory.createFixtures();
});

it('should allow transfer between accounts', () => {
const { bank, aliceUserId, bobUserId, aliceAccountId, bobAccountId } = fixtures;
it("should allow transfer between accounts", () => {
const { bank, aliceUserId, bobUserId, aliceAccountId, bobAccountId } =
fixtures;

// Initial balances
const aliceAccount = bank.getAccount(aliceAccountId);
Expand All @@ -26,19 +27,27 @@ describe('Bank Transfer Tests', () => {
expect(bobAccount.getBalance()).toBe(800);
});


it('should not allow transfer with insufficient funds', () => {
it("should not allow transfer with insufficient funds", () => {
const { bank, aliceUserId, bobUserId } = fixtures;

expect(() => {
bank.send(aliceUserId, bobUserId, 2000);
}).toThrow('Insufficient funds');
}).toThrow("Insufficient funds");
});

it('should allow transfer with negative balance', () => {
const { bank, bankAllowsNegative, aliceUserId, bobUserId, aliceAccountAllowsNegativeId, bobAccountId } = fixtures;

const aliceAccountAllowsNegative = bankAllowsNegative.getAccount(aliceAccountAllowsNegativeId);
it("should allow transfer with negative balance", () => {
const {
bank,
bankAllowsNegative,
aliceUserId,
bobUserId,
aliceAccountAllowsNegativeId,
bobAccountId,
} = fixtures;

const aliceAccountAllowsNegative = bankAllowsNegative.getAccount(
aliceAccountAllowsNegativeId
);
const bobBankId = bank.getId();
const bobAccount = bank.getAccount(bobAccountId);

Expand Down
46 changes: 24 additions & 22 deletions src/__tests__/simple.test.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,59 @@
import { describe, it, expect } from 'vitest';
import User from '@/models/user';
import Bank from '@/models/bank';
import BankAccount from '@/models/bank-account';
import TransactionService from '@/services/TransactionService';


describe('Banks', () => {
it('has a bank class', () => {
import { describe, it, expect } from "vitest";
import User from "@/models/user";
import Bank from "@/models/bank";
import BankAccount from "@/models/bank-account";
import TransactionService from "@/services/TransactionService";

describe("Banks", () => {
it("has a bank class", () => {
expect(Bank).toBeDefined();
});

it('can create a bank', () => {
it("can create a bank", () => {
const bank = Bank.create();
expect(bank.getId()).toBeDefined();
});

it('can create a bank account', () => {
it("can create a bank account", () => {
const bank = Bank.create();
const bankAccount = bank.createAccount(100);
expect(bankAccount.getId()).toBeDefined();
});
});


describe('Users', () => {
it('has a user class', () => {
describe("Users", () => {
it("has a user class", () => {
expect(User).toBeDefined();
});

it('can create a user', () => {
const user = User.create('Firstname Lastname', []);
it("can create a user", () => {
const user = User.create("Firstname Lastname", []);
expect(user).toBeDefined();
});

it('can create a user with accounts', () => {
it("can create a user with accounts", () => {
const bank = Bank.create();
const bankAccount1Id = bank.createAccount(100).getId();
const bankAccount2Id = bank.createAccount(200).getId();
const bankAccount3Id = bank.createAccount(300).getId();

const user = User.create('Firstname Lastname', [bankAccount1Id, bankAccount2Id, bankAccount3Id]);
const user = User.create("Firstname Lastname", [
bankAccount1Id,
bankAccount2Id,
bankAccount3Id,
]);
expect(user).toBeDefined();
});
});

describe('Accounts', () => {
it('has an account class', () => {
describe("Accounts", () => {
it("has an account class", () => {
expect(BankAccount).toBeDefined();
});
});

describe('Transactions', () => {
it('has a transaction service', () => {
describe("Transactions", () => {
it("has a transaction service", () => {
expect(TransactionService).toBeDefined();
});
});
31 changes: 31 additions & 0 deletions src/helpers/TestFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// ...existing imports...

export class TestFactory {
static createFixtures(): TestFixtures {
GlobalRegistry.clear();

// Create users first
const aliceUser = User.create("Alice");
const bobUser = User.create("Bob");

// Create accounts and associate with users
const aliceAccount = bank.createAccount(1000, aliceUser.getId());
const bobAccount = bank.createAccount(500, bobUser.getId());
const aliceAccountAllowsNegative = bankAllowsNegative.createAccount(
200,
aliceUser.getId()
);

// ...rest of your code...

return {
bank,
bankAllowsNegative,
aliceUserId: aliceUser.getId(),
bobUserId: bobUser.getId(),
aliceAccountId: aliceAccount.getId(),
bobAccountId: bobAccount.getId(),
aliceAccountAllowsNegativeId: aliceAccountAllowsNegative.getId(),
};
}
}
51 changes: 51 additions & 0 deletions src/models/bank-account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { BankAccountId } from "@/types/Common";

export default class BankAccount {
private id: BankAccountId;
private balance: number;
private isNegativeAllowed: boolean;

constructor(initialBalance: number, isNegativeAllowed: boolean = false) {
this.id = this.generateId();
this.balance = initialBalance;
this.isNegativeAllowed = isNegativeAllowed;
}

private generateId(): BankAccountId {
return `account_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

getId(): BankAccountId {
return this.id;
}

getBalance(): number {
return this.balance;
}

deposit(amount: number): void {
if (amount <= 0) {
throw new Error("Deposit amount must be positive");
}
this.balance += amount;
}

withdraw(amount: number): void {
if (amount <= 0) {
throw new Error("Withdrawal amount must be positive");
}

if (!this.isNegativeAllowed && this.balance < amount) {
throw new Error("Insufficient funds");
}

this.balance -= amount;
}

setBalance(balance: number): void {
if (!this.isNegativeAllowed && balance < 0) {
throw new Error("Negative balance not allowed");
}
this.balance = balance;
}
}
104 changes: 104 additions & 0 deletions src/models/bank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { BankAccountId, UserId } from "@/types/Common";
import BankAccount from "./bank-account";
import GlobalRegistry from "@/services/GlobalRegistry";

interface BankOptions {
isNegativeAllowed?: boolean;
}

export default class Bank {
private id: string;
private accounts: Map<BankAccountId, BankAccount>;
private isNegativeAllowed: boolean;

constructor(options: BankOptions = {}) {
this.id = this.generateId();
this.accounts = new Map();
this.isNegativeAllowed = options.isNegativeAllowed || false;
GlobalRegistry.registerBank(this);
}

private generateId(): string {
return `bank_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

static create(options?: BankOptions): Bank {
return new Bank(options);
}

getId(): string {
return this.id;
}

createAccount(initialBalance: number): BankAccount {
const account = new BankAccount(initialBalance, this.isNegativeAllowed);
this.accounts.set(account.getId(), account);
return account;
}

getAccount(accountId: BankAccountId): BankAccount {
const account = this.accounts.get(accountId);
if (!account) {
throw new Error(`Account ${accountId} not found`);
}
return account;
}

hasAccount(accountId: BankAccountId): boolean {
return this.accounts.has(accountId);
}

send(
fromUserId: UserId,
toUserId: UserId,
amount: number,
toBankId?: string
): void {
const fromUser = GlobalRegistry.getUser(fromUserId);
const toUser = GlobalRegistry.getUser(toUserId);

if (!fromUser || !toUser) {
throw new Error("User not found");
}

const fromAccountIds = fromUser.getAccountIds();
const toAccountIds = toUser.getAccountIds();

if (fromAccountIds.length === 0 || toAccountIds.length === 0) {
throw new Error("User has no accounts");
}

// Find accounts that belong to this bank
const fromAccountId = fromAccountIds.find((id) => this.hasAccount(id));
const toAccountId = toAccountIds.find((id) => this.hasAccount(id));

if (!fromAccountId) {
throw new Error("Sender has no account in this bank");
}

const fromAccount = this.getAccount(fromAccountId);

// Determine target bank and account
let toBank: Bank;
let toAccount: BankAccount;

if (toBankId && toBankId !== this.id) {
toBank = GlobalRegistry.getBank(toBankId);
const targetAccountId = toAccountIds.find((id) => toBank.hasAccount(id));
if (!targetAccountId) {
throw new Error("Recipient has no account in target bank");
}
toAccount = toBank.getAccount(targetAccountId);
} else {
if (!toAccountId) {
throw new Error("Recipient has no account in this bank");
}
toBank = this;
toAccount = this.getAccount(toAccountId);
}

// Perform the transfer
fromAccount.withdraw(amount);
toAccount.deposit(amount);
}
}
3 changes: 3 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as User } from "./user";
export { default as Bank } from "./bank";
export { default as BankAccount } from "./bank-account";
45 changes: 45 additions & 0 deletions src/models/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { UserId, BankAccountId } from "@/types/Common";
import GlobalRegistry from "@/services/GlobalRegistry";

export default class User {
private id: UserId;
private name: string;
private accountIds: BankAccountId[];

constructor(name: string, accountIds: BankAccountId[]) {
this.id = this.generateId();
this.name = name;
this.accountIds = accountIds;
GlobalRegistry.registerUser(this);
}

private generateId(): UserId {
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}

static create(name: string, accountIds: BankAccountId[]): User {
return new User(name, accountIds);
}

getId(): UserId {
return this.id;
}

getName(): string {
return this.name;
}

getAccountIds(): BankAccountId[] {
return [...this.accountIds];
}

addAccount(accountId: BankAccountId): void {
if (!this.accountIds.includes(accountId)) {
this.accountIds.push(accountId);
}
}

removeAccount(accountId: BankAccountId): void {
this.accountIds = this.accountIds.filter((id) => id !== accountId);
}
}
Loading