mirror of
https://github.com/BradNut/TofuStack
synced 2025-09-08 17:40:26 +00:00
added test for LoginRequestsService
This commit is contained in:
parent
3d56a22295
commit
d387a16bbe
7 changed files with 96 additions and 10 deletions
|
|
@ -6,6 +6,7 @@ import { container } from 'tsyringe';
|
||||||
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
|
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
|
||||||
import { IamController } from './controllers/iam.controller';
|
import { IamController } from './controllers/iam.controller';
|
||||||
import { config } from './common/config';
|
import { config } from './common/config';
|
||||||
|
import { UsersController } from './controllers/users.controller';
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Client Request */
|
/* Client Request */
|
||||||
|
|
@ -36,7 +37,6 @@ app.use(verifyOrigin).use(validateAuthSession);
|
||||||
const routes = app
|
const routes = app
|
||||||
.route('/iam', container.resolve(IamController).routes())
|
.route('/iam', container.resolve(IamController).routes())
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Exports */
|
/* Exports */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
|
||||||
11
src/lib/server/api/mockTest.ts
Normal file
11
src/lib/server/api/mockTest.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Scrypt } from "oslo/password";
|
||||||
|
|
||||||
|
|
||||||
|
export async function hash(value: string) {
|
||||||
|
const scrypt = new Scrypt()
|
||||||
|
return scrypt.hash(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verify(hashedValue: string, value: string) {
|
||||||
|
return new Scrypt().verify(hashedValue, value);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
import { DatabaseProvider } from "../providers";
|
import { DatabaseProvider } from "../providers";
|
||||||
import { and, eq, lte, type InferInsertModel } from "drizzle-orm";
|
import { and, eq, gte, lte, type InferInsertModel } from "drizzle-orm";
|
||||||
import type { Repository } from "../interfaces/repository.interface";
|
import type { Repository } from "../interfaces/repository.interface";
|
||||||
import { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
|
import { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
|
||||||
import { emailVerificationsTable } from "../infrastructure/database/tables/email-verifications.table";
|
import { emailVerificationsTable } from "../infrastructure/database/tables/email-verifications.table";
|
||||||
|
|
@ -24,7 +24,7 @@ export class EmailVerificationsRepository implements Repository {
|
||||||
return this.db.select().from(emailVerificationsTable).where(
|
return this.db.select().from(emailVerificationsTable).where(
|
||||||
and(
|
and(
|
||||||
eq(emailVerificationsTable.userId, userId),
|
eq(emailVerificationsTable.userId, userId),
|
||||||
lte(emailVerificationsTable.expiresAt, new Date())
|
gte(emailVerificationsTable.expiresAt, new Date())
|
||||||
)).then(takeFirst)
|
)).then(takeFirst)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe";
|
||||||
import { DatabaseProvider } from "../providers";
|
import { DatabaseProvider } from "../providers";
|
||||||
import type { Repository } from "../interfaces/repository.interface";
|
import type { Repository } from "../interfaces/repository.interface";
|
||||||
import { and, eq, gte, type InferInsertModel } from "drizzle-orm";
|
import { and, eq, gte, type InferInsertModel } from "drizzle-orm";
|
||||||
import { takeFirst } from "../infrastructure/database/utils";
|
import { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
|
||||||
import { loginRequestsTable } from "../infrastructure/database/tables/login-requests.table";
|
import { loginRequestsTable } from "../infrastructure/database/tables/login-requests.table";
|
||||||
|
|
||||||
export type CreateLoginRequest = Pick<InferInsertModel<typeof loginRequestsTable>, 'email' | 'expiresAt' | 'hashedToken'>;
|
export type CreateLoginRequest = Pick<InferInsertModel<typeof loginRequestsTable>, 'email' | 'expiresAt' | 'hashedToken'>;
|
||||||
|
|
@ -15,7 +15,7 @@ export class LoginRequestsRepository implements Repository {
|
||||||
return this.db.insert(loginRequestsTable).values(data).onConflictDoUpdate({
|
return this.db.insert(loginRequestsTable).values(data).onConflictDoUpdate({
|
||||||
target: loginRequestsTable.email,
|
target: loginRequestsTable.email,
|
||||||
set: data
|
set: data
|
||||||
})
|
}).returning().then(takeFirstOrThrow)
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOneByEmail(email: string) {
|
async findOneByEmail(email: string) {
|
||||||
|
|
|
||||||
|
|
@ -17,16 +17,16 @@ export class LoginRequestsService {
|
||||||
@inject(TokensService) private readonly tokensService: TokensService,
|
@inject(TokensService) private readonly tokensService: TokensService,
|
||||||
@inject(MailerService) private readonly mailerService: MailerService,
|
@inject(MailerService) private readonly mailerService: MailerService,
|
||||||
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
|
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
|
||||||
@inject(LoginRequestsRepository) private readonly loginRequetsRepository: LoginRequestsRepository,
|
@inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async create(data: RegisterEmailDto) {
|
async create(data: RegisterEmailDto) {
|
||||||
// generate a token, expiry date, and hash
|
// generate a token, expiry date, and hash
|
||||||
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
|
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
|
||||||
// save the login request to the database - ensuring we save the hashedToken
|
// save the login request to the database - ensuring we save the hashedToken
|
||||||
await this.loginRequetsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
|
await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
|
||||||
// send the login request email
|
// send the login request email
|
||||||
this.mailerService.sendLoginRequest({
|
await this.mailerService.sendLoginRequest({
|
||||||
to: data.email,
|
to: data.email,
|
||||||
props: { token: token }
|
props: { token: token }
|
||||||
});
|
});
|
||||||
|
|
@ -57,7 +57,7 @@ export class LoginRequestsService {
|
||||||
private async fetchValidRequest(email: string, token: string) {
|
private async fetchValidRequest(email: string, token: string) {
|
||||||
return await this.db.transaction(async (trx) => {
|
return await this.db.transaction(async (trx) => {
|
||||||
// fetch the login request
|
// fetch the login request
|
||||||
const loginRequest = await this.loginRequetsRepository.trxHost(trx).findOneByEmail(email)
|
const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email)
|
||||||
if (!loginRequest) return null;
|
if (!loginRequest) return null;
|
||||||
|
|
||||||
// check if the token is valid
|
// check if the token is valid
|
||||||
|
|
@ -65,7 +65,7 @@ export class LoginRequestsService {
|
||||||
if (!isValidRequest) return null
|
if (!isValidRequest) return null
|
||||||
|
|
||||||
// if the token is valid, burn the request
|
// if the token is valid, burn the request
|
||||||
await this.loginRequetsRepository.trxHost(trx).deleteById(loginRequest.id);
|
await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id);
|
||||||
return loginRequest
|
return loginRequest
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
src/lib/server/api/tests/login-requests.service.test.ts
Normal file
72
src/lib/server/api/tests/login-requests.service.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import 'reflect-metadata';
|
||||||
|
import { LoginRequestsService } from '../services/login-requests.service';
|
||||||
|
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { TokensService } from '../services/tokens.service';
|
||||||
|
import { MailerService } from '../services/mailer.service';
|
||||||
|
import { UsersRepository } from '../repositories/users.repository';
|
||||||
|
import { DatabaseProvider, LuciaProvider } from '../providers';
|
||||||
|
import { LoginRequestsRepository } from '../repositories/login-requests.repository';
|
||||||
|
import { PgDatabase } from 'drizzle-orm/pg-core';
|
||||||
|
import { container } from 'tsyringe';
|
||||||
|
|
||||||
|
describe('LoginRequestService', () => {
|
||||||
|
let service: LoginRequestsService;
|
||||||
|
let tokensService = vi.mocked(TokensService.prototype)
|
||||||
|
let mailerService = vi.mocked(MailerService.prototype);
|
||||||
|
let usersRepository = vi.mocked(UsersRepository.prototype);
|
||||||
|
let loginRequestsRepository = vi.mocked(LoginRequestsRepository.prototype);
|
||||||
|
let luciaProvider = vi.mocked(LuciaProvider);
|
||||||
|
let databaseProvider = vi.mocked(PgDatabase);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
service = container
|
||||||
|
.register<TokensService>(TokensService, { useValue: tokensService })
|
||||||
|
.register<MailerService>(MailerService, { useValue: mailerService })
|
||||||
|
.register<UsersRepository>(UsersRepository, { useValue: usersRepository })
|
||||||
|
.register(LoginRequestsRepository, { useValue: loginRequestsRepository })
|
||||||
|
.register(LuciaProvider, { useValue: luciaProvider })
|
||||||
|
.register(DatabaseProvider, { useValue: databaseProvider })
|
||||||
|
.resolve(LoginRequestsService);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
vi.resetAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Create', () => {
|
||||||
|
tokensService.generateTokenWithExpiryAndHash = vi.fn().mockResolvedValue({
|
||||||
|
token: "111",
|
||||||
|
expiry: new Date(),
|
||||||
|
hashedToken: "111"
|
||||||
|
} satisfies Awaited<ReturnType<typeof tokensService.generateTokenWithExpiryAndHash>>)
|
||||||
|
|
||||||
|
loginRequestsRepository.create = vi.fn().mockResolvedValue({
|
||||||
|
createdAt: new Date(),
|
||||||
|
email: 'me@test.com',
|
||||||
|
expiresAt: new Date(),
|
||||||
|
hashedToken: '111',
|
||||||
|
id: '1',
|
||||||
|
updatedAt: new Date()
|
||||||
|
} satisfies Awaited<ReturnType<typeof loginRequestsRepository.create>>)
|
||||||
|
|
||||||
|
mailerService.sendLoginRequest = vi.fn().mockResolvedValue(null)
|
||||||
|
|
||||||
|
const spy_mailerService_sendLoginRequest = vi.spyOn(mailerService, 'sendLoginRequest')
|
||||||
|
const spy_tokensService_generateTokenWithExpiryAndHash = vi.spyOn(tokensService, 'generateTokenWithExpiryAndHash')
|
||||||
|
const spy_loginRequestsRepository_create = vi.spyOn(loginRequestsRepository, 'create')
|
||||||
|
|
||||||
|
it('should resolve', async () => {
|
||||||
|
await expect(service.create({ email: "test" })).resolves.toBeUndefined()
|
||||||
|
})
|
||||||
|
it('should generate a token with expiry and hash', async () => {
|
||||||
|
expect(spy_tokensService_generateTokenWithExpiryAndHash).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should send an email with token', async () => {
|
||||||
|
expect(spy_mailerService_sendLoginRequest).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
it('should create a new login request record', async () => {
|
||||||
|
expect(spy_loginRequestsRepository_create).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
3
test-results/.last-run.json
Normal file
3
test-results/.last-run.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"status": "failed"
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue