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 { IamController } from './controllers/iam.controller';
|
||||
import { config } from './common/config';
|
||||
import { UsersController } from './controllers/users.controller';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Client Request */
|
||||
|
|
@ -36,7 +37,6 @@ app.use(verifyOrigin).use(validateAuthSession);
|
|||
const routes = app
|
||||
.route('/iam', container.resolve(IamController).routes())
|
||||
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* 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 { 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 { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
|
||||
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(
|
||||
and(
|
||||
eq(emailVerificationsTable.userId, userId),
|
||||
lte(emailVerificationsTable.expiresAt, new Date())
|
||||
gte(emailVerificationsTable.expiresAt, new Date())
|
||||
)).then(takeFirst)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe";
|
|||
import { DatabaseProvider } from "../providers";
|
||||
import type { Repository } from "../interfaces/repository.interface";
|
||||
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";
|
||||
|
||||
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({
|
||||
target: loginRequestsTable.email,
|
||||
set: data
|
||||
})
|
||||
}).returning().then(takeFirstOrThrow)
|
||||
}
|
||||
|
||||
async findOneByEmail(email: string) {
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@ export class LoginRequestsService {
|
|||
@inject(TokensService) private readonly tokensService: TokensService,
|
||||
@inject(MailerService) private readonly mailerService: MailerService,
|
||||
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
|
||||
@inject(LoginRequestsRepository) private readonly loginRequetsRepository: LoginRequestsRepository,
|
||||
@inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository,
|
||||
) { }
|
||||
|
||||
async create(data: RegisterEmailDto) {
|
||||
// generate a token, expiry date, and hash
|
||||
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
|
||||
// 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
|
||||
this.mailerService.sendLoginRequest({
|
||||
await this.mailerService.sendLoginRequest({
|
||||
to: data.email,
|
||||
props: { token: token }
|
||||
});
|
||||
|
|
@ -57,7 +57,7 @@ export class LoginRequestsService {
|
|||
private async fetchValidRequest(email: string, token: string) {
|
||||
return await this.db.transaction(async (trx) => {
|
||||
// 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;
|
||||
|
||||
// check if the token is valid
|
||||
|
|
@ -65,7 +65,7 @@ export class LoginRequestsService {
|
|||
if (!isValidRequest) return null
|
||||
|
||||
// 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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
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