Skip to content
Snippets Groups Projects
Commit 5d60e57b authored by irsyadnb's avatar irsyadnb
Browse files

feat: sso unit tests

parent 3014c0b8
1 merge request!2feat: sso unit tests
......@@ -2,8 +2,52 @@
PORT = 8080
# DATABASE
DATABASE_URL = postgresql://postgres:postgres@localhost:5432/kinerja?schema=public
DATABASE_URL = postgresql://postgres:postgres@localhost:5432/sso-server?schema=public
# CORS
ORIGIN = *
CREDENTIALS = true
\ No newline at end of file
ORIGIN1 = "http://localhost:8000"
ORIGIN2 = *
CREDENTIALS = true
# KEY
SSO_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA1haecO9shdFPg0hfndH5vAJfu7rrmQizIxCrY15rIep4b/Ae
+giQFo8H8MrTsuKlHAfx5Tb0yH7QR1k5VfTfBadI+XQUZR1gOdqqm47Qd/XlZbF6
YyzaAiwvh9PM12UjyEud6+XQOEkiD9Nh8RnZzHcR+15d+j1Rcx36KC2TismBe5Y/
LtGtUNkFoLfNokLPt44dOWlV5PeaSDZD6fTMVlH2GC09oFGDR4PYFNA0fdPZh5cw
5WLDyssItGbFMLjxPnw+IwvkT9jQedlwIrWIo1fAnHAEIzbfOsAXKjK+cwjw4gGA
lWznoaQdvNrxoOUy9lOjpdQmogK/Ws9P2h538wIDAQABAoIBAHac9Y+wtzm07A7a
jE9ORMULs6q5N0sEbOuikrJtX4Hc/HlYWnFBSUOWX1njpkLYG45aQIU0W46x5AeD
waaEEwR42I+M5rfW/LdRFXE2QP4VuNEM8bf845SMkpD4oec83ARfENHf8+sbacnG
3d7M6cTEu6u/buX0Dypuk2irR7F+Sz/kFuoIAmP9YzmhVhiRI9UwBIr4L31IgIiV
F3dtbguM6TJc4J604npW/iZqJ6Nt0dVhs2E0eO7tnaSkIrP2H40xOX939XYtPYPE
n0lTTl1tY0ds04kDHxnWn6SUNmiv6j4d0LLohBsjeEufYk+nq7yxaamsJlpBr6nh
Lk3oqCECgYEA+qvk3K5/odCitNtqN6uJFFDhXbaj5g4BdTlcBV0tJ9CUHDLHZZip
JY1lv+tXGNNkYC4pnHverHFnKnprgEz972EeJkxvDTRRoZ5tP3Sd1c8rIWfu+kYZ
XA1qX8EEvoatz+WLoUj5Thgc2TN94ykkQCjUjjnoUgkPMEoDfAXarosCgYEA2qOl
jGwNFsuqx5ARj4U4L2fx5np9KsN9BSsglCqTWLWp2dhE+2lobLxZZh2KqcbLxNnD
QDW9J/pudCiRj16K6bNl6gi3dL1Jl8nEd8M2HbxjU/i64yh+V4AJt4BHHs/QTMh0
zxd6+z58p2Ae9QIubmv+0f98xbt75mi1byCvMTkCgYBV9tseny0gBhe2ZESx4L66
293dsIPWolj3pXscT87rh4kzfmqJOehP+4S4Y3HUDrKulUYp5wT/KEjT9XWmY0D2
ddzMD4xJ//Y6scUPbOOv7kMBSs5Wv/F0cxlWyy/gUvmKgVL4Nblhgb1q81CptXM8
GYSDXfKBJ6Aw1EELqEpNbwKBgQCM241WUG6GVyRpeWm22w79i6wO3q8xE8zBva05
h8xyBGevD2QxzREXrKiz3yhshMTWx6zA+14oGXF7qH9OrIw2T/vCsWbv8NsuzTCk
L8H3ml0rxj0xB++Nk9GuxRgMw7nhHewTV39FylYoxwZqtsMPJMiApmbORSFnqeHp
/FaiEQKBgQDhKEnCZTQ/TjWjWyJr7AcdmV9NZpi967W4nioU1hfU65lR5KNzNwUG
iCEKtNwqK4QHK/KC4E2rEqU7DCCuun51Om39aMOsWto6yQRerz+2RSRKPxRvHoBT
6NaNYXOZtAxRidMw28o9CZWQwwig9VpsHy5+YwXcpXKYhrHLS+Tppg==
-----END RSA PRIVATE KEY-----"
SSO_PRIVATE_KEY_EXPIRATION = 60
KINERJA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv+MHaD2DeblxnOoUsrGu
l7puN0heokitsknGEXRcaIDaUehCPRdKFxzBV8OfrnoIHaNpyWEy3WQmPhDooXQp
S9P38gWkscB1FhczasCuuAIH6NHroNgNmZ0NVbHCsb682wkJ6A8JHrBDYLL5K/fm
Gve8OOq4R3MpCMZVbuTpASeahYdtwtxscwf6ih/GNaSmSLoPRL6ZZbkmVxxxv7Ry
9IvVTSA77unf4qk8lNfgKjYVpE07G43dMAXdlcAHxG3K0aAefK0oSLOvu13lrWZg
NLy5xoQU4yGj7NufeB8tdLX34kKM7f2JQik45LhFama+OuaRSmUs4WebeCL+TBH2
OwIDAQAB
-----END PUBLIC KEY-----"
KINERJA_URL = "http://localhost:8000"
\ No newline at end of file
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
moduleNameMapper: {
'^@config$': '<rootDir>/src/config',
'^@controllers/(.*)$': '<rootDir>/src/controllers/$1',
'^@dtos/(.*)$': '<rootDir>/src/dtos/$1',
'^@exceptions/(.*)$': '<rootDir>/src/exceptions/$1',
'^@interfaces/(.*)$': '<rootDir>/src/interfaces/$1',
'^@middlewares/(.*)$': '<rootDir>/src/middlewares/$1',
'^@routes/(.*)$': '<rootDir>/src/routes/$1',
'^@services/(.*)$': '<rootDir>/src/services/$1',
'^@utils/(.*)$': '<rootDir>/src/utils/$1',
'^@constants/(.*)$': '<rootDir>/src/constants/$1',
'^@helpers/(.*)$': '<rootDir>/src/helpers/$1',
'^types/http$': '<rootDir>/src/types/http.ts',
},
};
\ No newline at end of file
This diff is collapsed.
......@@ -10,7 +10,8 @@
"prisma:generate": "prisma generate",
"prisma:seed": "ts-node prisma/seed.ts",
"lint": "eslint --ignore-path .gitignore --ext .ts src/",
"format": "prettier --write src/"
"format": "prettier --write src/",
"test:unit-test": "jest"
},
"keywords": [],
"author": "",
......@@ -37,14 +38,19 @@
"@types/cors": "^2.8.17",
"@types/ejs": "^3.1.5",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.11.21",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^7.0.2",
"@typescript-eslint/parser": "^7.0.2",
"cross-env": "^7.0.3",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"prisma": "^5.10.2",
"supertest": "^7.0.0",
"ts-jest": "^29.1.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
}
......
import supertest from 'supertest';
import { App } from '../app';
import { HttpStatusCode } from '@constants/http.enum';
import { UserService } from '@services/user.service';
jest.mock('@config', () => ({
KINERJA_URL: 'http://localhost:8000',
SSO_PRIVATE_KEY:
`-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAhNfdOMRStmKXOfjPCNDFpWE9DXwj5vtKHjRzoUl3SOHn5xAJ
b7K69u/sqlmiH01yOBXVJLybmpONUqjXFXmU/k4lUQ8y+EnbVkvG55fNuHFPYjDI
/wxbbi71djWBRZTSs5ezLwEkNhY2Ii9lrYn9v59DZwMkmpGX8NYE24rJ/LNry76P
4xJ6gyyheR2dNfpQQ9xitsTdMY4qjkwJ2b/QHPbbhCsH4Qme61jxVcoYvxJoJA5u
3WXYct9QfCAjrstjiKPelsAgYcaRMqw3/ZPQyq3g/0EL/triVOUxGoEtKU0Sxpz+
Cj08EaGx62KHK9AR+Nn5PcboYToGZp9HXFV1jQIDAQABAoIBAFWnGWxQqUQPrZIj
ato0Qjuvxr27HSsBO3jeOO/1foPvpKbHIE503zf42o+721xzgwHGkfdt5KZ2HNj/
JC8khXyyxjzNn/+XH2U01rrcljgmVxjoe7B3NzyD6qCdPwyuOGyzk0TrZ324NI82
IEkUgBfdiIgS7rb6yQzv8ZHcmv/jfOuKjV7mt9FvKnQn4hu4foJ/d9G6AdQwpDQS
WYwGm1WTjza8prMxrzC5G9cs4ys6LIm3S5eyq0/M2QjffksvZPQZ6jN0WP9MFUHs
ckb28HRtHl0NatTM0DSpzlc4yQd+VWO+vxp+M3h5Y+xGAO7DUw7S9lmOmwkdBNXA
swIlO8ECgYEA4qT5N+0Ew+AwlRu2xeeHjZimLUoyhHFO6eZQ9HmE8dx8Lm5nF6hn
plGFLSyAG5a74Udja6VkhPITp5QkTunrn3R9fUMvGSsghCxXb4YstwRu7Uhz7EIs
MA7h+Zb0QZ0u/llB7J8SgBQ86Y13H6nDNcMXM1PcOGcKw2ZAAVoxtdsCgYEAlgym
j9WOZlOmrN2hQPEJn4Mw0JtIRLJe5hebnnLv5Pafe0vIOAyr2AROaERgm8mmi+EB
vEUaBp7LmzQ34ttHlI90nYmp9iGVWc7HPEfZiJjMjd9xFjljiwG2Cj1svKFNHHXD
i2a5bpjL2CH0b58ateiQ0j7MzeH/37msq3reQrcCgYAwmjwveA57isogT68kRYBW
JxQLxkuLB1lq408c/io6S5P9yQMmmaCQlA7BHzfhsrNbnhl97hvePqNkXmpi2dsD
eNGoWQ3ykcMJVRDzA4z3OmYnUFA7q/0Z2TcwdOSwZWq8dJukiIBJIw3k78dJ7PfF
wa5p/WPOXJp8SKMVJyhd3QKBgGA2AF7dyg7muErDWXAwncD/977qsm4KCTt0C76P
aC7hy+Tc9KaryUwbhsLohSGfLQEn51r22/c+vsLnuqXlgIJLcbTJ3ocJhaAuLCYE
1AD4KMEXcoWTirZJBJaQSDazHqSPBjFOTrwRkTMmonsoA5J8as/EoMGqlcy4W3Ug
tX8FAoGBAITPfq5jOkAa0iyVGDuQa91bl+35uIHlNadZn1l23PFQ/OXZBPC0lXdT
en0CjsFEXtH9fo6U+lL1dgP9ZP2EuI1SxVBvdCh0XtCdPEgxyjap0JJWGRaWLlbw
KkcqasUdKjS/z/qQxYc4FSpK9w3GDkxMQeWIiJ5wC78kdY2hHkqe
-----END RSA PRIVATE KEY-----`,
SSO_PRIVATE_KEY_EXPIRATION: 900,
}));
jest.mock('axios', () => ({
post: jest.fn().mockResolvedValueOnce({
headers: { 'set-cookie': ['mockedCookie'] },
data: { data: { redirectURL: 'http://example.com/redirected' } }
})
}));
describe('SSO Server API Tests', () => {
const application = new App();
it('should be a fail login', async () => {
const authInvalidData = {
email: 'director@salmanitb.com',
password: 'direct',
};
await supertest(application.app)
.post('/login?serviceURL=http://localhost:8000&callbackURL=http://localhost:8000/api/v1/sign-in-from-sso')
.send(authInvalidData)
.expect(HttpStatusCode.Unauthorized);
});
it('should be a success login', async () => {
const validAuthData = {
user_id: 1,
email: 'director@salmanitb.com',
password: '$2a$12$duoDr8CNoTx9utUDTk.APur9OZg4178Al2KqBikGIt0PSYwRxadBW',
position_id: 1,
dept_id: 1,
status: 1,
};
const mockUpdateApplication = jest.spyOn(UserService.prototype, 'getUserByEmail');
mockUpdateApplication.mockResolvedValueOnce(validAuthData);
// Make a POST request to simulate a login
await supertest(application.app)
.post('/login?serviceURL=http://localhost:8000&callbackURL=http://localhost:8000/api/v1/sign-in-from-sso')
.send({ email: 'director@salmanitb.com', password: 'director' })
.expect(HttpStatusCode.Found);
// Add assertions to ensure that it redirected to the expected URL
// Assertions for setting cookies can also be added if necessary
});
});
import { NextFunction, Response } from 'express';
import { ErrorMiddleware } from '.';
import { HttpException } from 'exceptions/http.exception';
import { HttpException } from '@exceptions/http.exception';
import { HttpStatusCode } from '@constants/http.enum';
import { ApiService } from 'types/http';
import { DataPayload, RequestWithUser } from '@interfaces/auth.interface';
......
......@@ -5,7 +5,7 @@ import { HttpStatusCode } from '@constants/http.enum';
import { PrismaClientKnownRequestError, PrismaClientUnknownRequestError } from '@prisma/client/runtime/library';
import { PrismaErrorForeignKeyConstraint, PrismaErrorUniqueConstraint } from '@interfaces/error.interface';
import { ResponseHelper } from '@helpers/response.helper';
import { HttpException } from 'exceptions/http.exception';
import { HttpException } from '@exceptions/http.exception';
abstract class ErrorMiddleware {
protected nextHandler?: ErrorMiddleware;
......
......@@ -28,7 +28,7 @@ class ValidationMiddleware {
await handler(req, res);
} catch (error) {
if (error instanceof HttpException && error.statusCode === HttpStatusCode.Unauthorized) {
res.render('login', {
res.status(HttpStatusCode.Unauthorized).render('login', {
title: "SSO Salman ITB | Login",
error: error.message,
email: email
......
......@@ -2,7 +2,7 @@ import { AuthController } from '@controllers/auth.controller';
import { Routes } from '@interfaces/route.interface';
import { ValidationMiddleware } from '@middlewares/index';
import { AuthService } from '@services/auth.service';
import { loginSchema } from 'dtos/auth.dto';
import { loginSchema } from '@dtos/auth.dto';
import { Router } from 'express';
export class AuthRoute implements Routes {
......
......@@ -3,7 +3,7 @@ import { RequestWithUser } from '@interfaces/auth.interface';
import { Routes } from '@interfaces/route.interface';
import { AuthMiddleware, ValidationMiddleware } from '@middlewares/index';
import { UserService } from '@services/user.service';
import { addUserSchema, resetPasswordSchema } from 'dtos/user.dto';
import { addUserSchema, resetPasswordSchema } from '@dtos/user.dto';
import { Response, NextFunction, Router } from 'express';
import { ApiService } from 'types/http';
......
......@@ -2,7 +2,7 @@ import { IUserLogin } from '@interfaces/auth.interface';
import { UserService } from './user.service';
import { compare } from 'bcryptjs';
import { Response } from 'express';
import { HttpException } from 'exceptions/http.exception';
import { HttpException } from '@exceptions/http.exception';
import { HttpStatusCode } from '@constants/http.enum';
import axios from 'axios';
import { AuthHelper } from '@helpers/auth.helper';
......@@ -49,7 +49,6 @@ export class AuthService {
}
const user = await this.userService.getUserByEmail(userLoginData.email);
// Check user
if (!user) {
throw new HttpException(HttpStatusCode.Unauthorized, 'Invalid credentials');
......
......@@ -31,7 +31,8 @@
"@services/*": ["services/*"],
"@utils/*": ["utils/*"],
"@constants/*": ["constants/*"],
"@helpers/*": ["helpers/*"]
"@helpers/*": ["helpers/*"],
"@types/*" : ["types/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.json", ".env"],
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment