Skip to content

Commit

Permalink
feat: refactor userFactory and authService.register
Browse files Browse the repository at this point in the history
  • Loading branch information
typeWolffo committed Jul 17, 2024
1 parent 92cf39b commit b1a3356
Show file tree
Hide file tree
Showing 13 changed files with 70 additions and 116 deletions.
4 changes: 2 additions & 2 deletions examples/common_nestjs_remix/apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json --detectOpenHandles --runInBand --verbose",
"test:e2e:watch": "jest --config ./test/jest-e2e.json --detectOpenHandles --runInBand --verbose --watch",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:e2e:watch": "jest --config ./test/jest-e2e.json --watch",
"db:migrate": "drizzle-kit migrate",
"db:generate": "drizzle-kit generate"
},
Expand Down
3 changes: 1 addition & 2 deletions examples/common_nestjs_remix/apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ import { AuthModule } from "./auth/auth.module";
import { UsersModule } from "./users/users.module";
import { JwtModule } from "@nestjs/jwt";
import jwtConfig from "./common/configuration/jwt";
import authConfig from "./common/configuration/auth";
import { APP_GUARD } from "@nestjs/core";
import { JwtAuthGuard } from "./common/guards/jwt-auth-guard";

@Module({
imports: [
ConfigModule.forRoot({
load: [database, jwtConfig, authConfig],
load: [database, jwtConfig],
isGlobal: true,
}),
DrizzlePostgresModule.registerAsync({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,23 @@ import { JwtService } from "@nestjs/jwt";
import { credentials, users } from "../../storage/schema";
import { DatabasePg } from "../../../src/common";
import { createUnitTest, TestContext } from "../../../test/create-unit-test";
import { createUsersFactory } from "../../../test/factory/user.factory";
import { createUserFactory } from "../../../test/factory/user.factory";
import { omit } from "lodash";
import hashPassword from "src/common/helpers/hashPassword";

describe("AuthService", () => {
let testContext: TestContext;
let authService: AuthService;
let jwtService: JwtService;
let db: DatabasePg;
const userFactory = createUsersFactory();
let userFactory: ReturnType<typeof createUserFactory>;

beforeAll(async () => {
testContext = await createUnitTest();
authService = testContext.getService(AuthService);
jwtService = testContext.getService(JwtService);
db = testContext.db;
});

afterAll(async () => {
if (testContext.container) {
await testContext.container.stop();
}
await testContext.module.close();
userFactory = createUserFactory(db);
});

afterEach(async () => {
Expand All @@ -36,24 +32,24 @@ describe("AuthService", () => {

describe("register", () => {
it("should register a new user successfully", async () => {
const { users: userData } = userFactory.build();
const user = userFactory.build();
const password = "password123";

const result = await authService.register(userData.email, password);

expect(result).toBeDefined();
expect(result.email).toBe(userData.email);
const result = await authService.register(user.email, password);

const [savedUser] = await db
.select()
.from(users)
.where(eq(users.email, userData.email));
.where(eq(users.email, user.email));
expect(savedUser).toBeDefined();

const [savedCredentials] = await db
.select()
.from(credentials)
.where(eq(credentials.userId, savedUser.id));

expect(result).toBeDefined();
expect(result.email).toBe(user.email);
expect(savedCredentials).toBeDefined();
expect(await bcrypt.compare(password, savedCredentials.password)).toBe(
true,
Expand All @@ -62,44 +58,35 @@ describe("AuthService", () => {

it("should throw ConflictException if user already exists", async () => {
const email = "existing@example.com";
await db.insert(users).values({ email });
const user = await userFactory.create({ email });

await expect(authService.register(email, "password123")).rejects.toThrow(
ConflictException,
);
await expect(
authService.register(user.email, "password123"),
).rejects.toThrow(ConflictException);
});
});

describe("login", () => {
it("should login user successfully", async () => {
const password = "password123";
const hashedPassword = await bcrypt.hash(password, 10);
const { users: userData, credentials: userCredentials } =
userFactory.build();

const [user] = await db.insert(users).values(userData).returning();
await db.insert(credentials).values({
...userCredentials,
userId: user.id,
password: hashedPassword,
});

(jwtService.signAsync as jest.Mock).mockResolvedValueOnce("access_token");
(jwtService.signAsync as jest.Mock).mockResolvedValueOnce(
"refresh_token",
);
const email = "example@test.com";
const user = await userFactory
.withCredentials({ password })
.create({ email });

const result = await authService.login({
email: userData.email,
email: user.email,
password,
});

expect(result).toEqual({
...user,
accessToken: "access_token",
refreshToken: "refresh_token",
const decodedToken = await jwtService.verifyAsync(result.accessToken);

expect(decodedToken.userId).toBe(user.id);
expect(result).toMatchObject({
...omit(user, "credentials"),
accessToken: expect.any(String),
refreshToken: expect.any(String),
});
expect(jwtService.signAsync).toHaveBeenCalledTimes(2);
});

it("should throw UnauthorizedException for invalid email", async () => {
Expand All @@ -112,18 +99,11 @@ describe("AuthService", () => {
});

it("should throw UnauthorizedException for invalid password", async () => {
const { users: userData, credentials: userCredentials } =
userFactory.build();
const [user] = await db.insert(users).values(userData).returning();
await db.insert(credentials).values({
...userCredentials,
userId: user.id,
password: await bcrypt.hash("correctpassword", 10),
});
const user = await userFactory.create({ email: "example@test.com" });

await expect(
authService.login({
email: userData.email,
email: user.email,
password: "wrongpassword",
}),
).rejects.toThrow(UnauthorizedException);
Expand All @@ -134,7 +114,7 @@ describe("AuthService", () => {
it("should validate user successfully", async () => {
const email = "test@example.com";
const password = "password123";
const hashedPassword = await bcrypt.hash(password, 10);
const hashedPassword = await hashPassword(password);

const [user] = await db.insert(users).values({ email }).returning();
await db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { eq } from "drizzle-orm";
import { DatabasePg } from "src/common";
import { credentials, users } from "../storage/schema";
import { UsersService } from "../users/users.service";
import hashPassword from "src/common/helpers/hashPassword";

@Injectable()
export class AuthService {
Expand All @@ -31,7 +32,7 @@ export class AuthService {
throw new ConflictException("User already exists");
}

const hashedPassword = await bcrypt.hash(password, 10);
const hashedPassword = await hashPassword(password);

return this.db.transaction(async (trx) => {
const [newUser] = await trx.insert(users).values({ email }).returning();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@ import {
ACCESS_TOKEN_EXPIRATION_TIME,
REFRESH_TOKEN_EXPIRATION_TIME,
} from "./consts";
import { ConfigService } from "@nestjs/config";

@Injectable()
export class TokenService {
constructor(private readonly configService: ConfigService) {}
setTokenCookies(
response: Response,
accessToken: string,
Expand All @@ -26,10 +24,7 @@ export class TokenService {
secure: true,
sameSite: "strict",
maxAge: REFRESH_TOKEN_EXPIRATION_TIME,
path: this.configService.get<string>(
"auth.refreshTokenPath",
"/auth/refresh-token",
),
path: "/auth/refresh",
});
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UserWithCredentials,
} from "../../../test/factory/user.factory";
import { DatabasePg } from "src/common";
import { castArray, omit } from "lodash";

describe("UsersController (e2e)", () => {
let app: INestApplication;
Expand Down Expand Up @@ -51,23 +52,22 @@ describe("UsersController (e2e)", () => {
.set("Cookie", cookies)
.expect(200);

expect(response.body.data).toBeDefined();
expect(response.body.data).toStrictEqual(
castArray(omit(testUser, "credentials")),
);
expect(Array.isArray(response.body.data)).toBe(true);
});
});

describe("GET /users/:id", () => {
it("should return a user by id", async () => {
console.log(testUser);
const response = await request(app.getHttpServer())
.get(`/users/${testUser.id}`)
.set("Cookie", cookies)
.expect(200);

console.log(response.body.data);

expect(response.body.data).toBeDefined();
expect(response.body.data.id).toBe(testUser.id);
expect(response.body.data).toStrictEqual(omit(testUser, "credentials"));
});

it("should return 404 for non-existent user", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,33 @@ import { credentials, users } from "../../storage/schema";
import { DatabasePg } from "src/common";
import { TestContext, createUnitTest } from "../../../test/create-unit-test";
import { UsersService } from "../users.service";
import { createUsersFactory } from "../../../test/factory/user.factory";
import { createUserFactory } from "../../../test/factory/user.factory";
import { truncateAllTables } from "../../../test/helpers/test-helpers";

describe("UsersService", () => {
let testContext: TestContext;
let usersService: UsersService;
let db: DatabasePg;
const userFactory = createUsersFactory();
let userFactory: ReturnType<typeof createUserFactory>;

beforeAll(async () => {
testContext = await createUnitTest();
usersService = testContext.getService(UsersService);
db = testContext.db;
userFactory = createUserFactory(db);
});

afterAll(async () => {
if (testContext.container) {
await testContext.container.stop();
}
await testContext.module.close();
await testContext.teardown();
});

afterEach(async () => {
await db.delete(credentials);
await db.delete(users);
await truncateAllTables(db);
});

describe("getUsers", () => {
it("should return all users", async () => {
const testUsers = [userFactory.build().users, userFactory.build().users];
const testUsers = Array.from({ length: 2 }, () => userFactory.build());
await db.insert(users).values(testUsers);

const result = await usersService.getUsers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
import { DatabasePg } from "src/common";
import { credentials, users } from "../storage/schema";
import hashPassword from "src/common/helpers/hashPassword";

@Injectable()
export class UsersService {
Expand Down Expand Up @@ -75,7 +76,7 @@ export class UsersService {
throw new UnauthorizedException("Invalid old password");
}

const hashedNewPassword = await bcrypt.hash(newPassword, 10);
const hashedNewPassword = await hashPassword(newPassword);
await this.db
.update(credentials)
.set({ password: hashedNewPassword })
Expand Down
9 changes: 4 additions & 5 deletions examples/common_nestjs_remix/apps/api/test/create-e2e-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import { AppModule } from "../src/app.module";
import { setupTestDatabase } from "./test-database";

export async function createE2ETest(customProviders: Provider[] = []) {
const { db } = await setupTestDatabase();
const { db, connectionString } = await setupTestDatabase();

process.env.DATABASE_URL = connectionString;

const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
providers: [...customProviders],
})
.overrideProvider("DB")
.useValue(db)
.compile();
}).compile();

const app = moduleFixture.createNestApplication();

Expand Down
Loading

0 comments on commit b1a3356

Please sign in to comment.