diff --git a/.gitignore b/.gitignore index 96606c0..c4a95ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -coverage \ No newline at end of file +coverage +/.idea/ +.idea \ No newline at end of file diff --git a/app.mock.test.js b/app.mock.test.js index 79b9449..41d6a30 100644 --- a/app.mock.test.js +++ b/app.mock.test.js @@ -1,22 +1,20 @@ + 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); - }) -}) - +// Mock the email validation to avoid the 2-second delay +jest.mock('./validation/validateEmail') const validateEmail = require('./validation/validateEmail') + const app = createApp(validateUsername, validatePassword, validateEmail) describe('given correct username and password', () => { + beforeEach(() => { + validateEmail.mockReturnValue(true) + }) + test('return status 200', async () => { const response = await request(app).post('/users').send({ username: 'Username', @@ -35,13 +33,39 @@ describe('given correct username and password', () => { expect(response.body.userId).toBeDefined(); }) - // test response content type? - // test response message - // test response user id value - // ... + test('returns correct userId value', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.body.userId).toBe('1') + }) + + test('returns success message', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.body.message).toBe('Valid User') + }) + + test('response content type is JSON', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.headers['content-type']).toMatch(/json/) + }) }) describe('given incorrect or missing username and password', () => { + beforeEach(() => { + validateEmail.mockReturnValue(false) + }) + test('return status 400', async () => { const response = await request(app).post('/users').send({ username: 'user', @@ -51,9 +75,220 @@ describe('given incorrect or missing username and password', () => { 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 - // ... + test('response does NOT have userId', async () => { + const response = await request(app).post('/users').send({ + username: 'user', + password: 'password', + email: 'not-an-email' + }) + expect(response.body.userId).toBeUndefined() + }) + + test('returns error message', async () => { + const response = await request(app).post('/users').send({ + username: 'user', + password: 'password', + email: 'not-an-email' + }) + expect(response.body.error).toBe('Invalid User') + }) + + test('rejects username that is too short', async () => { + const response = await request(app).post('/users').send({ + username: 'short', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + expect(response.body.error).toBe('Invalid User') + }) + + test('rejects password that is too short', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Pass1', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without uppercase letter', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without lowercase letter', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'PASSWORD123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without number', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'PasswordABC', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password with special characters', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password@123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects invalid email format', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'not-an-email' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects email without @ symbol', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'studentexample.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects email without domain extension', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing email', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing username', async () => { + const response = await request(app).post('/users').send({ + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing password', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) +}) + +describe('valid username requirements', () => { + beforeEach(() => { + validateEmail.mockReturnValue(true) + }) + + test('accepts username with 6 characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user12', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with 30 characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.with.dots123456789', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with letters and numbers', async () => { + const response = await request(app).post('/users').send({ + username: 'Username123', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with periods', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.test', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('rejects username with special characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user@name', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects username that is too long', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.with.too.many.characters.here', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) +}) + +describe('valid email requirements', () => { + beforeEach(() => { + validateEmail.mockReturnValue(true) + }) + + test('accepts valid email with .com domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'user@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts valid email with .edu domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@university.edu' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts valid email with .org domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'contact@organization.org' + }) + expect(response.statusCode).toBe(200) + }) }) \ No newline at end of file diff --git a/app.test.js b/app.test.js index f1b561d..67f3da4 100644 --- a/app.test.js +++ b/app.test.js @@ -25,10 +25,32 @@ describe('given correct username and password', () => { expect(response.body.userId).toBeDefined(); }) - // test response content type? - // test response message - // test response user id value - // ... + test('returns correct userId value', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.body.userId).toBe('1') + }) + + test('returns success message', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.body.message).toBe('Valid User') + }) + + test('response content type is JSON', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.headers['content-type']).toMatch(/json/) + }) }) describe('given incorrect or missing username and password', () => { @@ -41,9 +63,212 @@ describe('given incorrect or missing username and password', () => { 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 - // ... + test('response does NOT have userId', async () => { + const response = await request(app).post('/users').send({ + username: 'user', + password: 'password', + email: 'not-an-email' + }) + expect(response.body.userId).toBeUndefined() + }) + + test('returns error message', async () => { + const response = await request(app).post('/users').send({ + username: 'user', + password: 'password', + email: 'not-an-email' + }) + expect(response.body.error).toBe('Invalid User') + }) + + test('rejects username that is too short', async () => { + const response = await request(app).post('/users').send({ + username: 'short', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + expect(response.body.error).toBe('Invalid User') + }) + + test('rejects password that is too short', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Pass1', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without uppercase letter', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without lowercase letter', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'PASSWORD123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password without number', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'PasswordABC', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects password with special characters', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password@123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects invalid email format', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'not-an-email' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects email without @ symbol', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'studentexample.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects email without domain extension', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@example' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing email', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing username', async () => { + const response = await request(app).post('/users').send({ + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects missing password', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) +}) + +describe('valid username requirements', () => { + test('accepts username with 6 characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user12', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with 30 characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.with.dots123456789', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with letters and numbers', async () => { + const response = await request(app).post('/users').send({ + username: 'Username123', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts username with periods', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.test', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('rejects username with special characters', async () => { + const response = await request(app).post('/users').send({ + username: 'user@name', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) + + test('rejects username that is too long', async () => { + const response = await request(app).post('/users').send({ + username: 'user.name.with.too.many.characters.here', + password: 'Password123', + email: 'student@example.com' + }) + expect(response.statusCode).toBe(400) + }) +}) + +describe('valid email requirements', () => { + test('accepts valid email with .com domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'user@example.com' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts valid email with .edu domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'student@university.edu' + }) + expect(response.statusCode).toBe(200) + }) + + test('accepts valid email with .org domain', async () => { + const response = await request(app).post('/users').send({ + username: 'Username', + password: 'Password123', + email: 'contact@organization.org' + }) + expect(response.statusCode).toBe(200) + }) }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f07de70..77a5c91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,6 +48,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1211,6 +1212,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1566,6 +1568,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2131,6 +2134,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^2.0.0", "@eslint/js": "8.35.0", @@ -5920,6 +5924,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "peer": true, "requires": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -6749,7 +6754,8 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -6999,6 +7005,7 @@ "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "peer": true, "requires": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7393,6 +7400,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz", "integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^2.0.0", "@eslint/js": "8.35.0", @@ -9979,4 +9987,4 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" } } -} +} \ No newline at end of file diff --git a/validation/validatePassword.js b/validation/validatePassword.js index 44d8a8b..50912a4 100644 --- a/validation/validatePassword.js +++ b/validation/validatePassword.js @@ -1,4 +1,6 @@ function validatePassword(password) { + if (!password || typeof password !== 'string') return false; + const validLength = password.length >= 8; const hasNumber = /[0-9]/g.test(password); const hasUpperCaseLetters = /[A-Z]/g.test(password); @@ -7,4 +9,4 @@ function validatePassword(password) { return validLength && hasNumber && hasLowerCaseLetters && hasUpperCaseLetters && !hasSpecialCharacters; } -module.exports = validatePassword; +module.exports = validatePassword; \ No newline at end of file diff --git a/validation/validateUsername.js b/validation/validateUsername.js index 30213b1..889fbc1 100644 --- a/validation/validateUsername.js +++ b/validation/validateUsername.js @@ -1,7 +1,9 @@ function validateUsername(username) { + if (!username || typeof username !== 'string') return false; + const validLength = username.length >= 6 && username.length <=30; const allowedcharacters = /^[a-zA-Z0-9.]+$/g.test(username); return validLength && allowedcharacters; } -module.exports = validateUsername; +module.exports = validateUsername; \ No newline at end of file