Skip to content

Commit d387a16

Browse files
committed
added test for LoginRequestsService
1 parent 3d56a22 commit d387a16

File tree

7 files changed

+96
-10
lines changed

7 files changed

+96
-10
lines changed

src/lib/server/api/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { container } from 'tsyringe';
66
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
77
import { IamController } from './controllers/iam.controller';
88
import { config } from './common/config';
9+
import { UsersController } from './controllers/users.controller';
910

1011
/* -------------------------------------------------------------------------- */
1112
/* Client Request */
@@ -36,7 +37,6 @@ app.use(verifyOrigin).use(validateAuthSession);
3637
const routes = app
3738
.route('/iam', container.resolve(IamController).routes())
3839

39-
4040
/* -------------------------------------------------------------------------- */
4141
/* Exports */
4242
/* -------------------------------------------------------------------------- */

src/lib/server/api/mockTest.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Scrypt } from "oslo/password";
2+
3+
4+
export async function hash(value: string) {
5+
const scrypt = new Scrypt()
6+
return scrypt.hash(value);
7+
}
8+
9+
export function verify(hashedValue: string, value: string) {
10+
return new Scrypt().verify(hashedValue, value);
11+
}

src/lib/server/api/repositories/email-verifications.repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { inject, injectable } from "tsyringe";
22
import { DatabaseProvider } from "../providers";
3-
import { and, eq, lte, type InferInsertModel } from "drizzle-orm";
3+
import { and, eq, gte, lte, type InferInsertModel } from "drizzle-orm";
44
import type { Repository } from "../interfaces/repository.interface";
55
import { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
66
import { emailVerificationsTable } from "../infrastructure/database/tables/email-verifications.table";
@@ -24,7 +24,7 @@ export class EmailVerificationsRepository implements Repository {
2424
return this.db.select().from(emailVerificationsTable).where(
2525
and(
2626
eq(emailVerificationsTable.userId, userId),
27-
lte(emailVerificationsTable.expiresAt, new Date())
27+
gte(emailVerificationsTable.expiresAt, new Date())
2828
)).then(takeFirst)
2929
}
3030

src/lib/server/api/repositories/login-requests.repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { inject, injectable } from "tsyringe";
22
import { DatabaseProvider } from "../providers";
33
import type { Repository } from "../interfaces/repository.interface";
44
import { and, eq, gte, type InferInsertModel } from "drizzle-orm";
5-
import { takeFirst } from "../infrastructure/database/utils";
5+
import { takeFirst, takeFirstOrThrow } from "../infrastructure/database/utils";
66
import { loginRequestsTable } from "../infrastructure/database/tables/login-requests.table";
77

88
export type CreateLoginRequest = Pick<InferInsertModel<typeof loginRequestsTable>, 'email' | 'expiresAt' | 'hashedToken'>;
@@ -15,7 +15,7 @@ export class LoginRequestsRepository implements Repository {
1515
return this.db.insert(loginRequestsTable).values(data).onConflictDoUpdate({
1616
target: loginRequestsTable.email,
1717
set: data
18-
})
18+
}).returning().then(takeFirstOrThrow)
1919
}
2020

2121
async findOneByEmail(email: string) {

src/lib/server/api/services/login-requests.service.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@ export class LoginRequestsService {
1717
@inject(TokensService) private readonly tokensService: TokensService,
1818
@inject(MailerService) private readonly mailerService: MailerService,
1919
@inject(UsersRepository) private readonly usersRepository: UsersRepository,
20-
@inject(LoginRequestsRepository) private readonly loginRequetsRepository: LoginRequestsRepository,
20+
@inject(LoginRequestsRepository) private readonly loginRequestsRepository: LoginRequestsRepository,
2121
) { }
2222

2323
async create(data: RegisterEmailDto) {
2424
// generate a token, expiry date, and hash
2525
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
2626
// save the login request to the database - ensuring we save the hashedToken
27-
await this.loginRequetsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
27+
await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
2828
// send the login request email
29-
this.mailerService.sendLoginRequest({
29+
await this.mailerService.sendLoginRequest({
3030
to: data.email,
3131
props: { token: token }
3232
});
@@ -57,15 +57,15 @@ export class LoginRequestsService {
5757
private async fetchValidRequest(email: string, token: string) {
5858
return await this.db.transaction(async (trx) => {
5959
// fetch the login request
60-
const loginRequest = await this.loginRequetsRepository.trxHost(trx).findOneByEmail(email)
60+
const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email)
6161
if (!loginRequest) return null;
6262

6363
// check if the token is valid
6464
const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token);
6565
if (!isValidRequest) return null
6666

6767
// if the token is valid, burn the request
68-
await this.loginRequetsRepository.trxHost(trx).deleteById(loginRequest.id);
68+
await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id);
6969
return loginRequest
7070
})
7171
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import 'reflect-metadata';
2+
import { LoginRequestsService } from '../services/login-requests.service';
3+
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
4+
import { TokensService } from '../services/tokens.service';
5+
import { MailerService } from '../services/mailer.service';
6+
import { UsersRepository } from '../repositories/users.repository';
7+
import { DatabaseProvider, LuciaProvider } from '../providers';
8+
import { LoginRequestsRepository } from '../repositories/login-requests.repository';
9+
import { PgDatabase } from 'drizzle-orm/pg-core';
10+
import { container } from 'tsyringe';
11+
12+
describe('LoginRequestService', () => {
13+
let service: LoginRequestsService;
14+
let tokensService = vi.mocked(TokensService.prototype)
15+
let mailerService = vi.mocked(MailerService.prototype);
16+
let usersRepository = vi.mocked(UsersRepository.prototype);
17+
let loginRequestsRepository = vi.mocked(LoginRequestsRepository.prototype);
18+
let luciaProvider = vi.mocked(LuciaProvider);
19+
let databaseProvider = vi.mocked(PgDatabase);
20+
21+
beforeAll(() => {
22+
service = container
23+
.register<TokensService>(TokensService, { useValue: tokensService })
24+
.register<MailerService>(MailerService, { useValue: mailerService })
25+
.register<UsersRepository>(UsersRepository, { useValue: usersRepository })
26+
.register(LoginRequestsRepository, { useValue: loginRequestsRepository })
27+
.register(LuciaProvider, { useValue: luciaProvider })
28+
.register(DatabaseProvider, { useValue: databaseProvider })
29+
.resolve(LoginRequestsService);
30+
});
31+
32+
33+
afterAll(() => {
34+
vi.resetAllMocks()
35+
})
36+
37+
describe('Create', () => {
38+
tokensService.generateTokenWithExpiryAndHash = vi.fn().mockResolvedValue({
39+
token: "111",
40+
expiry: new Date(),
41+
hashedToken: "111"
42+
} satisfies Awaited<ReturnType<typeof tokensService.generateTokenWithExpiryAndHash>>)
43+
44+
loginRequestsRepository.create = vi.fn().mockResolvedValue({
45+
createdAt: new Date(),
46+
47+
expiresAt: new Date(),
48+
hashedToken: '111',
49+
id: '1',
50+
updatedAt: new Date()
51+
} satisfies Awaited<ReturnType<typeof loginRequestsRepository.create>>)
52+
53+
mailerService.sendLoginRequest = vi.fn().mockResolvedValue(null)
54+
55+
const spy_mailerService_sendLoginRequest = vi.spyOn(mailerService, 'sendLoginRequest')
56+
const spy_tokensService_generateTokenWithExpiryAndHash = vi.spyOn(tokensService, 'generateTokenWithExpiryAndHash')
57+
const spy_loginRequestsRepository_create = vi.spyOn(loginRequestsRepository, 'create')
58+
59+
it('should resolve', async () => {
60+
await expect(service.create({ email: "test" })).resolves.toBeUndefined()
61+
})
62+
it('should generate a token with expiry and hash', async () => {
63+
expect(spy_tokensService_generateTokenWithExpiryAndHash).toBeCalledTimes(1)
64+
})
65+
it('should send an email with token', async () => {
66+
expect(spy_mailerService_sendLoginRequest).toHaveBeenCalledTimes(1)
67+
})
68+
it('should create a new login request record', async () => {
69+
expect(spy_loginRequestsRepository_create).toBeCalledTimes(1)
70+
})
71+
})
72+
});

test-results/.last-run.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"status": "failed"
3+
}

0 commit comments

Comments
 (0)