diff --git a/app.mock.test.js b/app.mock.test.js index 79b9449..66284c3 100644 --- a/app.mock.test.js +++ b/app.mock.test.js @@ -1,59 +1,117 @@ -const createApp = require('./app') -const request = require('supertest') -const validateUsername = require('./validation/validateUsername') -const validatePassword = require('./validation/validatePassword') - -//Mock validateEmail to isolate tests -jest.mock('./validation/validateEmail', () => { - return jest.fn((email) => { - //Simulate real world simulation - if (!email || typeof email !== 'string') return false; - const re = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i; - return re.test(email); - }) -}) - -const validateEmail = require('./validation/validateEmail') -const app = createApp(validateUsername, validatePassword, validateEmail) - -describe('given correct username and password', () => { - test('return status 200', async () => { - const response = await request(app).post('/users').send({ - username: 'Username', - password: 'Password123', - email: 'student@example.com' - }) - expect(response.statusCode).toBe(200) - }) - - test('returns userId', async () => { - const response = await request(app).post('/users').send({ - username: 'Username', - password: 'Password123', - email: 'student@example.com' - }) - expect(response.body.userId).toBeDefined(); - }) - - // test response content type? - // test response message - // test response user id value - // ... -}) - -describe('given incorrect or missing username and password', () => { - test('return status 400', async () => { - const response = await request(app).post('/users').send({ - username: 'user', - password: 'password', - email: 'not-an-email' - }) - expect(response.statusCode).toBe(400) - }) - - // test response message - // test that response does NOT have userId - // test incorrect username or password according to requirements - // test missing username or password - // ... +const createApp = require('./app') +const request = require('supertest') +const validateUsername = require('./validation/validateUsername') +const validatePassword = require('./validation/validatePassword') + +// Mock email validation but still execute the real validator logic +// without the artificial 2-second delay, so the mocked run keeps 100% coverage. +jest.mock('./validation/validateEmail', () => { + const actualValidateEmail = jest.requireActual('./validation/validateEmail') + + return jest.fn((email) => { + const originalDateNow = Date.now + + Date.now = jest.fn() + .mockReturnValueOnce(0) + .mockReturnValue(2001) + + try { + return actualValidateEmail(email) + } finally { + Date.now = originalDateNow + } + }) +}) + +const validateEmail = require('./validation/validateEmail') +const app = createApp(validateUsername, validatePassword, validateEmail) + +describe('POST /users', () => { + beforeEach(() => { + validateEmail.mockClear() + }) + + test('returns 200 and valid user payload for valid input', async () => { + const response = await request(app).post('/users').send({ + username: 'User.Name123', + password: 'Password123', + email: 'student@example.com' + }) + + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toMatch(/json/) + expect(response.body).toEqual({ + userId: '1', + message: 'Valid User' + }) + }) + + test.each([ + ['username is shorter than 6 characters', { + username: 'user', + password: 'Password123', + email: 'student@example.com' + }], + ['username contains special characters', { + username: 'user!23', + password: 'Password123', + email: 'student@example.com' + }], + ['password is shorter than 8 characters', { + username: 'Valid.User', + password: 'Pass123', + email: 'student@example.com' + }], + ['password is missing an uppercase letter', { + username: 'Valid.User', + password: 'password123', + email: 'student@example.com' + }], + ['password is missing a lowercase letter', { + username: 'Valid.User', + password: 'PASSWORD123', + email: 'student@example.com' + }], + ['password is missing a number', { + username: 'Valid.User', + password: 'Password', + email: 'student@example.com' + }], + ['password contains special characters', { + username: 'Valid.User', + password: 'Password123!', + email: 'student@example.com' + }], + ['email is missing @', { + username: 'Valid.User', + password: 'Password123', + email: 'studentexample.com' + }], + ['email is missing a valid domain extension', { + username: 'Valid.User', + password: 'Password123', + email: 'student@example' + }], + ['username is missing', { + password: 'Password123', + email: 'student@example.com' + }], + ['password is missing', { + username: 'Valid.User', + email: 'student@example.com' + }], + ['email is missing', { + username: 'Valid.User', + password: 'Password123' + }] + ])('returns 400 and error payload when %s', async (_scenario, payload) => { + const response = await request(app).post('/users').send(payload) + + expect(response.statusCode).toBe(400) + expect(response.headers['content-type']).toMatch(/json/) + expect(response.body).toEqual({ + error: 'Invalid User' + }) + expect(response.body.userId).toBeUndefined() + }) }) \ No newline at end of file diff --git a/app.test.js b/app.test.js index f1b561d..3c2c559 100644 --- a/app.test.js +++ b/app.test.js @@ -6,44 +6,88 @@ const validateEmail = require('./validation/validateEmail') const app = createApp(validateUsername, validatePassword, validateEmail) -describe('given correct username and password', () => { - test('return status 200', async () => { +describe('POST /users', () => { + test('returns 200 and valid user payload for valid input', async () => { const response = await request(app).post('/users').send({ - username: 'Username', + username: 'User.Name123', password: 'Password123', email: 'student@example.com' }) + expect(response.statusCode).toBe(200) + expect(response.headers['content-type']).toMatch(/json/) + expect(response.body).toEqual({ + userId: '1', + message: 'Valid User' + }) }) - test('returns userId', async () => { - const response = await request(app).post('/users').send({ - username: 'Username', + test.each([ + ['username is shorter than 6 characters', { + username: 'user', password: 'Password123', email: 'student@example.com' - }) - expect(response.body.userId).toBeDefined(); - }) - - // test response content type? - // test response message - // test response user id value - // ... -}) + }], + ['username contains special characters', { + username: 'user!23', + password: 'Password123', + email: 'student@example.com' + }], + ['password is shorter than 8 characters', { + username: 'Valid.User', + password: 'Pass123', + email: 'student@example.com' + }], + ['password is missing an uppercase letter', { + username: 'Valid.User', + password: 'password123', + email: 'student@example.com' + }], + ['password is missing a lowercase letter', { + username: 'Valid.User', + password: 'PASSWORD123', + email: 'student@example.com' + }], + ['password is missing a number', { + username: 'Valid.User', + password: 'Password', + email: 'student@example.com' + }], + ['password contains special characters', { + username: 'Valid.User', + password: 'Password123!', + email: 'student@example.com' + }], + ['email is missing @', { + username: 'Valid.User', + password: 'Password123', + email: 'studentexample.com' + }], + ['email is missing a valid domain extension', { + username: 'Valid.User', + password: 'Password123', + email: 'student@example' + }], + ['username is missing', { + password: 'Password123', + email: 'student@example.com' + }], + ['password is missing', { + username: 'Valid.User', + email: 'student@example.com' + }], + ['email is missing', { + username: 'Valid.User', + password: 'Password123' + }] + ])('returns 400 and error payload when %s', async (_scenario, payload) => { + const response = await request(app).post('/users').send(payload) -describe('given incorrect or missing username and password', () => { - test('return status 400', async () => { - const response = await request(app).post('/users').send({ - username: 'user', - password: 'password', - email: 'not-an-email' - }) expect(response.statusCode).toBe(400) + expect(response.headers['content-type']).toMatch(/json/) + expect(response.body).toEqual({ + error: 'Invalid User' + }) + expect(response.body.userId).toBeUndefined() }) - - // test response message - // test that response does NOT have userId - // test incorrect username or password according to requirements - // test missing username or password - // ... }) \ No newline at end of file diff --git a/validation/validatePassword.js b/validation/validatePassword.js index 44d8a8b..1416d54 100644 --- a/validation/validatePassword.js +++ b/validation/validatePassword.js @@ -1,9 +1,13 @@ function validatePassword(password) { + if (typeof password !== 'string') { + return false; + } + const validLength = password.length >= 8; const hasNumber = /[0-9]/g.test(password); const hasUpperCaseLetters = /[A-Z]/g.test(password); const hasLowerCaseLetters = /[a-z]/g.test(password); - const hasSpecialCharacters = /[\W]/g.test(password); + const hasSpecialCharacters = /[^a-zA-Z0-9]/g.test(password); return validLength && hasNumber && hasLowerCaseLetters && hasUpperCaseLetters && !hasSpecialCharacters; } diff --git a/validation/validateUsername.js b/validation/validateUsername.js index 30213b1..ff6d5ee 100644 --- a/validation/validateUsername.js +++ b/validation/validateUsername.js @@ -1,4 +1,8 @@ function validateUsername(username) { + if (typeof username !== 'string') { + return false; + } + const validLength = username.length >= 6 && username.length <=30; const allowedcharacters = /^[a-zA-Z0-9.]+$/g.test(username);