mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
test: create initial unit tests
This commit is contained in:
parent
be7fb7d13a
commit
e476243b85
16 changed files with 1177 additions and 18 deletions
99
src/auth/auth.controller.spec.ts
Normal file
99
src/auth/auth.controller.spec.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import type { Response } from "express";
|
||||
import { Logger } from "../logger/logger.service";
|
||||
import { User } from "../users/user.entity";
|
||||
import { AuthSession } from "./auth-session.entity";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { COOKIE_REFRESH_TOKEN } from "./constants";
|
||||
|
||||
describe("AuthController", () => {
|
||||
let controller: AuthController;
|
||||
let authService: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [AuthController],
|
||||
providers: [
|
||||
{ provide: AuthService, useFactory: () => ({}) },
|
||||
{ provide: Logger, useClass: Logger },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<AuthController>(AuthController);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(controller).toBeDefined();
|
||||
expect(authService).toBeDefined();
|
||||
});
|
||||
|
||||
describe("spotifyCallback", () => {
|
||||
let user: User;
|
||||
let res: Response;
|
||||
let refreshToken: string;
|
||||
|
||||
beforeEach(() => {
|
||||
user = { id: "user" } as User;
|
||||
res = {
|
||||
statusCode: 200,
|
||||
cookie: jest.fn(),
|
||||
redirect: jest.fn(),
|
||||
} as unknown as Response;
|
||||
|
||||
refreshToken = "REFRESH_TOKEN";
|
||||
authService.createSession = jest.fn().mockResolvedValue({ refreshToken });
|
||||
});
|
||||
|
||||
it("creates session for user", async () => {
|
||||
await controller.spotifyCallback(user, res);
|
||||
|
||||
expect(authService.createSession).toHaveBeenCalledTimes(1);
|
||||
expect(authService.createSession).toHaveBeenCalledWith(user);
|
||||
});
|
||||
|
||||
it("sets refresh token as cookie", async () => {
|
||||
await controller.spotifyCallback(user, res);
|
||||
|
||||
expect(res.cookie).toHaveBeenCalledTimes(1);
|
||||
expect(res.cookie).toHaveBeenCalledWith(
|
||||
COOKIE_REFRESH_TOKEN,
|
||||
refreshToken,
|
||||
{ httpOnly: true }
|
||||
);
|
||||
});
|
||||
|
||||
it("redirects to login success page", async () => {
|
||||
await controller.spotifyCallback(user, res);
|
||||
|
||||
expect(res.redirect).toHaveBeenCalledTimes(1);
|
||||
expect(res.redirect).toHaveBeenCalledWith(
|
||||
"/login/success?source=spotify"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("refreshAccessToken", () => {
|
||||
let session: AuthSession;
|
||||
let accessToken: string;
|
||||
|
||||
beforeEach(() => {
|
||||
session = { id: "session" } as AuthSession;
|
||||
accessToken = "ACCESS_TOKEN";
|
||||
|
||||
authService.createAccessToken = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ accessToken });
|
||||
});
|
||||
|
||||
it("returns new access token", async () => {
|
||||
await expect(controller.refreshAccessToken(session)).resolves.toEqual({
|
||||
accessToken,
|
||||
});
|
||||
|
||||
expect(authService.createAccessToken).toHaveBeenCalledTimes(1);
|
||||
expect(authService.createAccessToken).toHaveBeenCalledWith(session);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -6,7 +6,6 @@ import {
|
|||
UseFilters,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import type { Response } from "express";
|
||||
import { User } from "../users/user.entity";
|
||||
import { AuthSession } from "./auth-session.entity";
|
||||
|
|
@ -22,10 +21,7 @@ import { SpotifyAuthFilter } from "./spotify.filter";
|
|||
|
||||
@Controller("api/v1/auth")
|
||||
export class AuthController {
|
||||
constructor(
|
||||
private readonly authService: AuthService,
|
||||
private readonly config: ConfigService
|
||||
) {}
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Get("spotify")
|
||||
@UseGuards(SpotifyAuthGuard)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,319 @@
|
|||
import { ForbiddenException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { User } from "../users/user.entity";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { AuthSession } from "./auth-session.entity";
|
||||
import { AuthSessionRepository } from "./auth-session.repository";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { LoginDto } from "./dto/login.dto";
|
||||
|
||||
describe("AuthService", () => {
|
||||
let service: AuthService;
|
||||
let config: ConfigService;
|
||||
let usersService: UsersService;
|
||||
let jwtService: JwtService;
|
||||
let authSessionRepository: AuthSessionRepository;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
providers: [
|
||||
AuthService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useFactory: () => ({ get: jest.fn().mockReturnValue("FOOBAR") }),
|
||||
},
|
||||
{ provide: UsersService, useFactory: () => ({}) },
|
||||
{ provide: JwtService, useFactory: () => ({}) },
|
||||
{ provide: AuthSessionRepository, useFactory: () => ({}) },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
config = module.get<ConfigService>(ConfigService);
|
||||
usersService = module.get<UsersService>(UsersService);
|
||||
jwtService = module.get<JwtService>(JwtService);
|
||||
authSessionRepository = module.get<AuthSessionRepository>(
|
||||
AuthSessionRepository
|
||||
);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(service).toBeDefined();
|
||||
expect(config).toBeDefined();
|
||||
expect(usersService).toBeDefined();
|
||||
expect(jwtService).toBeDefined();
|
||||
expect(authSessionRepository).toBeDefined();
|
||||
});
|
||||
|
||||
describe("spotifyLogin", () => {
|
||||
let loginDto: LoginDto;
|
||||
let user: User;
|
||||
|
||||
beforeEach(() => {
|
||||
loginDto = {
|
||||
accessToken: "ACCESS_TOKEN",
|
||||
refreshToken: "REFRESH_TOKEN",
|
||||
profile: {
|
||||
id: "FOOBAR",
|
||||
displayName: "Max Mustermann",
|
||||
photos: ["https://example.com/profile.jpeg"],
|
||||
},
|
||||
};
|
||||
|
||||
user = {
|
||||
id: "USER",
|
||||
} as User;
|
||||
|
||||
service.allowedByUserFilter = jest.fn().mockReturnValue(true);
|
||||
usersService.createOrUpdate = jest.fn().mockResolvedValue(user);
|
||||
});
|
||||
|
||||
it("returns the logged in user", async () => {
|
||||
await expect(service.spotifyLogin(loginDto)).resolves.toEqual(user);
|
||||
});
|
||||
|
||||
it("verifies that the user is allowed by the user filter", async () => {
|
||||
await service.spotifyLogin(loginDto);
|
||||
|
||||
expect(service.allowedByUserFilter).toHaveBeenCalledTimes(1);
|
||||
expect(service.allowedByUserFilter).toHaveBeenCalledWith(
|
||||
loginDto.profile.id
|
||||
);
|
||||
});
|
||||
|
||||
it("throws ForbiddenException is user is not in the filter", async () => {
|
||||
service.allowedByUserFilter = jest.fn().mockReturnValue(false);
|
||||
|
||||
await expect(service.spotifyLogin(loginDto)).rejects.toThrow(
|
||||
ForbiddenException
|
||||
);
|
||||
});
|
||||
|
||||
it("updates the user profile with data from spotify", async () => {
|
||||
await service.spotifyLogin(loginDto);
|
||||
|
||||
expect(usersService.createOrUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(usersService.createOrUpdate).toHaveBeenLastCalledWith({
|
||||
displayName: "Max Mustermann",
|
||||
photo: "https://example.com/profile.jpeg",
|
||||
spotify: {
|
||||
id: "FOOBAR",
|
||||
accessToken: "ACCESS_TOKEN",
|
||||
refreshToken: "REFRESH_TOKEN",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("createSession", () => {
|
||||
let user: User;
|
||||
let session: AuthSession;
|
||||
let refreshToken: string;
|
||||
|
||||
beforeEach(() => {
|
||||
user = { id: "USER" } as User;
|
||||
session = {
|
||||
id: "SESSION",
|
||||
user,
|
||||
} as AuthSession;
|
||||
refreshToken = "REFRESH_TOKEN";
|
||||
|
||||
authSessionRepository.create = jest
|
||||
.fn()
|
||||
.mockReturnValue({ id: "SESSION" });
|
||||
|
||||
authSessionRepository.save = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
// @ts-expect-error
|
||||
service.createRefreshToken = jest
|
||||
.fn()
|
||||
.mockResolvedValue({ refreshToken });
|
||||
});
|
||||
|
||||
it("returns the session and refresh token", async () => {
|
||||
await expect(service.createSession(user)).resolves.toEqual({
|
||||
session,
|
||||
refreshToken,
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a new session", async () => {
|
||||
await service.createSession(user);
|
||||
|
||||
expect(authSessionRepository.create).toHaveBeenCalledTimes(1);
|
||||
expect(authSessionRepository.save).toHaveBeenCalledTimes(1);
|
||||
expect(authSessionRepository.save).toHaveBeenCalledWith(session);
|
||||
});
|
||||
|
||||
it("it creates a refresh token", async () => {
|
||||
await service.createSession(user);
|
||||
|
||||
// @ts-expect-error
|
||||
expect(service.createRefreshToken).toHaveBeenCalledTimes(1);
|
||||
// @ts-expect-error
|
||||
expect(service.createRefreshToken).toHaveBeenCalledWith(session);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createRefreshToken", () => {
|
||||
let session: AuthSession;
|
||||
let refreshToken: string;
|
||||
|
||||
beforeEach(() => {
|
||||
session = {
|
||||
user: { id: "USER", displayName: "Max Mustermann" } as User,
|
||||
id: "SESSION",
|
||||
} as AuthSession;
|
||||
|
||||
refreshToken = "REFRESH_TOKEN";
|
||||
|
||||
jwtService.signAsync = jest.fn().mockResolvedValue(refreshToken);
|
||||
});
|
||||
|
||||
it("returns the refreshToken", async () => {
|
||||
// @ts-expect-error
|
||||
await expect(service.createRefreshToken(session)).resolves.toEqual({
|
||||
refreshToken,
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a token with correct values", async () => {
|
||||
// @ts-expect-error
|
||||
service.sessionExpirationTime = "EXPIRATION_TIME";
|
||||
|
||||
// @ts-expect-error
|
||||
await service.createRefreshToken(session);
|
||||
|
||||
expect(jwtService.signAsync).toHaveBeenCalledTimes(1);
|
||||
expect(jwtService.signAsync).toHaveBeenCalledWith(
|
||||
{ sub: "USER", name: "Max Mustermann" },
|
||||
{
|
||||
jwtid: session.id,
|
||||
expiresIn: "EXPIRATION_TIME",
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createAccessToken", () => {
|
||||
let session: AuthSession;
|
||||
let accessToken: string;
|
||||
|
||||
beforeEach(() => {
|
||||
session = {
|
||||
id: "AUTH_SESSION",
|
||||
user: {
|
||||
id: "USER",
|
||||
displayName: "Max Mustermann",
|
||||
photo: "https://example.com/picture.jpg",
|
||||
},
|
||||
} as AuthSession;
|
||||
|
||||
accessToken = "ACCESS_TOKEN";
|
||||
|
||||
jwtService.signAsync = jest.fn().mockResolvedValue(accessToken);
|
||||
});
|
||||
|
||||
it("returns the access token", async () => {
|
||||
await expect(service.createAccessToken(session)).resolves.toEqual({
|
||||
accessToken,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws ForbiddenException if the session is revoked", async () => {
|
||||
session.revokedAt = new Date("2020-01-01T00:00:00Z");
|
||||
|
||||
await expect(service.createAccessToken(session)).rejects.toThrow(
|
||||
ForbiddenException
|
||||
);
|
||||
});
|
||||
|
||||
it("creates a token with correct values", async () => {
|
||||
await service.createAccessToken(session);
|
||||
|
||||
expect(jwtService.signAsync).toHaveBeenCalledTimes(1);
|
||||
expect(jwtService.signAsync).toHaveBeenLastCalledWith({
|
||||
sub: "USER",
|
||||
name: "Max Mustermann",
|
||||
picture: "https://example.com/picture.jpg",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("findSession", () => {
|
||||
let session: AuthSession;
|
||||
|
||||
beforeEach(() => {
|
||||
session = { id: "AUTH_SESSION" } as AuthSession;
|
||||
|
||||
authSessionRepository.findOne = jest.fn().mockResolvedValue(session);
|
||||
});
|
||||
|
||||
it("returns the session", async () => {
|
||||
await expect(service.findSession("AUTH_SESSION")).resolves.toEqual(
|
||||
session
|
||||
);
|
||||
|
||||
expect(authSessionRepository.findOne).toHaveBeenCalledTimes(1);
|
||||
expect(authSessionRepository.findOne).toHaveBeenLastCalledWith(
|
||||
"AUTH_SESSION"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findUser", () => {
|
||||
let user: User;
|
||||
|
||||
beforeEach(() => {
|
||||
user = { id: "USER" } as User;
|
||||
|
||||
usersService.findById = jest.fn().mockResolvedValue(user);
|
||||
});
|
||||
|
||||
it("returns the user", async () => {
|
||||
await expect(service.findUser("USER")).resolves.toEqual(user);
|
||||
|
||||
expect(usersService.findById).toHaveBeenCalledTimes(1);
|
||||
expect(usersService.findById).toHaveBeenLastCalledWith("USER");
|
||||
});
|
||||
});
|
||||
|
||||
describe("allowedByUserFilter", () => {
|
||||
it("returns true if user filter is unset", () => {
|
||||
// @ts-expect-error
|
||||
service.userFilter = null;
|
||||
|
||||
expect(service.allowedByUserFilter("123")).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true if the user filter is an empty string", () => {
|
||||
// @ts-expect-error
|
||||
service.userFilter = "";
|
||||
|
||||
expect(service.allowedByUserFilter("123")).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true if the user is the only user in filter", () => {
|
||||
// @ts-expect-error
|
||||
service.userFilter = "123";
|
||||
|
||||
expect(service.allowedByUserFilter("123")).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns true if the user is one of the users in filter", () => {
|
||||
// @ts-expect-error
|
||||
service.userFilter = "123,456";
|
||||
|
||||
expect(service.allowedByUserFilter("456")).toEqual(true);
|
||||
});
|
||||
|
||||
it("returns false if the user is not in the filter", () => {
|
||||
// @ts-expect-error
|
||||
service.userFilter = "123,456";
|
||||
|
||||
expect(service.allowedByUserFilter("789")).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
69
src/auth/strategies/refresh-token.strategy.spec.ts
Normal file
69
src/auth/strategies/refresh-token.strategy.spec.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { ForbiddenException, UnauthorizedException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AuthService } from "../auth.service";
|
||||
import { RefreshTokenStrategy } from "./refresh-token.strategy";
|
||||
|
||||
describe("RefreshTokenStrategy", () => {
|
||||
let strategy: RefreshTokenStrategy;
|
||||
let authService: AuthService;
|
||||
let configService: ConfigService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
RefreshTokenStrategy,
|
||||
{ provide: AuthService, useFactory: () => ({}) },
|
||||
{
|
||||
provide: ConfigService,
|
||||
useFactory: () => ({ get: jest.fn().mockReturnValue("foobar") }),
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
strategy = module.get<RefreshTokenStrategy>(RefreshTokenStrategy);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
configService = module.get<ConfigService>(ConfigService);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(strategy).toBeDefined();
|
||||
expect(authService).toBeDefined();
|
||||
expect(configService).toBeDefined();
|
||||
});
|
||||
|
||||
describe("validate", () => {
|
||||
let session;
|
||||
let payload;
|
||||
|
||||
beforeEach(() => {
|
||||
payload = { jti: "123-foo-bar" };
|
||||
|
||||
session = { mock: "Session" };
|
||||
authService.findSession = jest.fn().mockResolvedValue(session);
|
||||
});
|
||||
|
||||
it("return session from authService", async () => {
|
||||
await expect(strategy.validate(payload)).resolves.toEqual(session);
|
||||
|
||||
expect(authService.findSession).toHaveBeenCalledTimes(1);
|
||||
expect(authService.findSession).toHaveBeenCalledWith(payload.jti);
|
||||
});
|
||||
|
||||
it("throws UnauthorizedException if session does not exist", async () => {
|
||||
authService.findSession = jest.fn().mockResolvedValue(undefined);
|
||||
|
||||
await expect(strategy.validate(payload)).rejects.toThrow(
|
||||
UnauthorizedException
|
||||
);
|
||||
});
|
||||
|
||||
it("throws ForbiddenException is session is revoked", async () => {
|
||||
session.revokedAt = "2021-01-01";
|
||||
|
||||
await expect(strategy.validate(payload)).rejects.toThrow(
|
||||
ForbiddenException
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue