diff --git a/packages/core/src/infrastructure/repositories/mock-articles.repository.ts b/packages/core/src/infrastructure/repositories/mock-articles.repository.ts new file mode 100644 index 0000000..8ed6800 --- /dev/null +++ b/packages/core/src/infrastructure/repositories/mock-articles.repository.ts @@ -0,0 +1,46 @@ +import { injectable } from "inversify"; + +import type { IArticlesRepository } from "@/application/repositories/articles.repository.interface.js"; +import type { Article } from "@/entities/models/article.js"; + +@injectable() +export class MockArticlesRepository implements IArticlesRepository { + private _articles: Article[] = []; + + async getArticle(id: string): Promise
{ + return this._articles.find((a) => a.id === id); + } + + async getArticles(options?: { + status?: string; + authorId?: string; + limit?: number; + offset?: number; + }): Promise { + let result = [...this._articles]; + if (options?.status) { + result = result.filter((a) => a.status === options.status); + } + if (options?.authorId) { + result = result.filter((a) => a.authorId === options.authorId); + } + const offset = options?.offset ?? 0; + const limit = options?.limit ?? 50; + return result.slice(offset, offset + limit); + } + + async createArticle(input: Article): Promise
{ + this._articles.push(input); + return input; + } + + async updateArticle( + id: string, + input: Partial
+ ): Promise
{ + const index = this._articles.findIndex((a) => a.id === id); + if (index === -1) return undefined; + this._articles[index] = { ...this._articles[index]!, ...input }; + return this._articles[index]; + } +} diff --git a/packages/core/src/infrastructure/repositories/mock-users.repository.ts b/packages/core/src/infrastructure/repositories/mock-users.repository.ts new file mode 100644 index 0000000..968f838 --- /dev/null +++ b/packages/core/src/infrastructure/repositories/mock-users.repository.ts @@ -0,0 +1,25 @@ +import { injectable } from "inversify"; + +import type { IUsersRepository } from "@/application/repositories/users.repository.interface.js"; +import type { User } from "@/entities/models/user.js"; + +@injectable() +export class MockUsersRepository implements IUsersRepository { + private _users: User[] = [ + { id: "1", username: "alice", passwordHash: "hashed_password_alice" }, + { id: "2", username: "bob", passwordHash: "hashed_password_bob" }, + ]; + + async getUser(id: string): Promise { + return this._users.find((u) => u.id === id); + } + + async getUserByUsername(username: string): Promise { + return this._users.find((u) => u.username === username); + } + + async createUser(input: User): Promise { + this._users.push(input); + return input; + } +} diff --git a/packages/core/src/infrastructure/services/mock-auth.service.ts b/packages/core/src/infrastructure/services/mock-auth.service.ts new file mode 100644 index 0000000..9999b55 --- /dev/null +++ b/packages/core/src/infrastructure/services/mock-auth.service.ts @@ -0,0 +1,72 @@ +import { inject, injectable } from "inversify"; + +import type { IAuthenticationService } from "@/application/services/auth.service.interface.js"; +import type { IUsersRepository } from "@/application/repositories/users.repository.interface.js"; +import { UnauthenticatedError } from "@/entities/errors/auth.js"; +import { sessionSchema, type Session } from "@/entities/models/session.js"; +import type { Cookie } from "@/entities/models/cookie.js"; +import type { User } from "@/entities/models/user.js"; +import { DI_SYMBOLS } from "@/di/types.js"; +import { SESSION_COOKIE } from "@/config.js"; + +@injectable() +export class MockAuthenticationService implements IAuthenticationService { + private _sessions: Record = {}; + + constructor( + @inject(DI_SYMBOLS.IUsersRepository) + private _usersRepository: IUsersRepository + ) {} + + generateUserId(): string { + return (Math.random() + 1).toString(36).substring(7); + } + + async hashPassword(password: string): Promise { + return `hashed_${password}`; + } + + async verifyPassword(hash: string, password: string): Promise { + return hash === `hashed_${password}`; + } + + async validateSession( + sessionId: string + ): Promise<{ user: User; session: Session }> { + const result = this._sessions[sessionId]; + if (!result) { + throw new UnauthenticatedError("Unauthenticated"); + } + const user = await this._usersRepository.getUser(result.user.id); + if (!user) { + throw new UnauthenticatedError("Unauthenticated"); + } + return { user, session: result.session }; + } + + async createSession( + user: User + ): Promise<{ session: Session; cookie: Cookie }> { + const session = sessionSchema.parse({ + id: "session_" + user.id, + userId: user.id, + expiresAt: new Date(Date.now() + 86400000 * 7), + }); + const cookie: Cookie = { + name: SESSION_COOKIE, + value: session.id, + attributes: {}, + }; + this._sessions[session.id] = { session, user }; + return { session, cookie }; + } + + async invalidateSession( + sessionId: string + ): Promise<{ blankCookie: Cookie }> { + delete this._sessions[sessionId]; + return { + blankCookie: { name: SESSION_COOKIE, value: "", attributes: {} }, + }; + } +} diff --git a/packages/core/src/infrastructure/services/mock-telemetry.service.ts b/packages/core/src/infrastructure/services/mock-telemetry.service.ts new file mode 100644 index 0000000..5b6e1c1 --- /dev/null +++ b/packages/core/src/infrastructure/services/mock-telemetry.service.ts @@ -0,0 +1,10 @@ +import { injectable } from "inversify"; + +import type { ITelemetryService } from "@/application/services/telemetry.service.interface.js"; + +@injectable() +export class MockTelemetryService implements ITelemetryService { + async startSpan(_name: string, fn: () => T | Promise): Promise { + return fn(); + } +}