본문 바로가기

notAnymore/Udemy API

Make authentication API with TDD [express]

The first thing I will do is defining User model. after this, authentication process is wating(login, sign up, etc).

 

Let's just put minimum fieds that we need. [name, email, role, password]

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');		// used when hashing password.

const UserSchema = new mongoose.Schema({
	name: {
		type: String,
		required: [true, 'Please add a name']
	},
	email: {
		type: String,
		required: [true,'Please add an email'],
		unique: true,
		match: [
     	 /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,
      	'Please add a valid email'
    	]
	},
	role: {
		type: String,
		enum: ['user', 'instructor'],
		defalut: 'user'
	},
	password: {
		type: String,
		required: [true, 'Please add a password'],
		minlength: 6,
		select: false
	},
	createdAt: {
		type: Date,
		default: Date.now
	}
});

UserSchema.pre('save', async function(next) {	// pre middleware
    if (!this.isModified('password')) {
        next();
    }

    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
});

module.exports = mongoose.model('User', UserSchema);

To do hash, we need to install 'bcryptjs'

> npm install bcryptjs

"pre('save', ~);" is a middleware process before saving user model. In this case, this hashs inputed password.

 

now we defiend minimum user modelSchema. so the next, we just make directorys controllers, routes.

//udemy/controllers/auth.js
const User = require('../models/User');
//udemy/routes/auth.js
const express = require('express');
const router = express.Router();

module.exports = router;

Connect route.

//udemy.server.js

//Route files
const auth = require('.routes/auth');

//Mount routers
app.use('/api/v1/auths', auth);

If http request "/api/v1/auths/" comes, goes to udemy/routes/auth.js file.

Now then It is time to make Test Code first time.

we will use mocha, chai, supertest for test. Install these.

> npm install mocha supertest chai

then put in package.json

"script": {
	"start"~,
    "dev":~,
    "test": "mocha"		// put this code.
  },

Test Requirement: POST /register()

Register User
- If input valid datas client can create new user. 200 success.
- If input invalid datas, 400 error occurs.
- If input same email already exist, 409 error occurs.

Make file "/udemy/tests/user.spec.js". and bring utils.

//udemy/tests/user.speck.js

const User = require('../models/User');
const request = request("supertest");
const expect = require("chai").expect;
const app = require("../server"); // connect with application.

Next is making test code.

describe("/api/v1/auth", () =>
  beforeEach(async () => {
    await User.deleteMany({});
  });
  
  describe("POST /", () => {
    // success register
    it("[Success] register/create a user", async () => {
      const res = await request(app)
        .post("/api/v1/auth/register")
        .send({
          name: "test",
          email: "test@gmail.com",
          password: "123412345",
          role: "user"
      });
      expect(res.status).to.equal(200);
      expect(res.body).to.have.property("token");
    });
    
    it("[Fail] invalid input", async() => {
      const res = await request(app)
      	.post("/api/v1/auth/register")
        .send({
          name: "test",
          email: "testgamilcom",
          password: "1234556",
          role: "user"
        });
        
      it("[Fail] duplicated error", async () => {
       const user = {
            name: "test", email: "test@gmail.com", password: "123456", role: "user"};
            
            await User.insertMany(user);
           
            const res = await request(app)
              .post("/api/v1/auth/register")
              .send({
                name: "test",
                email: "test@gamil.com",
                password: "123456",
                role: "user"
              });
            expect(res.status).to.be.equal(409);
            expect(res.body.error).to.be.equal('Duplicate field value entered');
          });
      });
});

If we do test with this console "npm test tests/user.spec.js", We got this log.

PS C:\Users\moonq\Desktop\udemy> npm test tests/user.spec.js

> udemy@1.0.0 test C:\Users\moonq\Desktop\udemy
> mocha --timeout 10000 "tests/user.spec.js"

Server running in development mode on port 5000


  api/v1/auth
    POST /
MongoDB Connected: cluster0-shard-00-00-lamdv.mongodb.net
POST /api/v1/auth/regitser 404 1.813 ms - 160
      1) should register/create a user


  0 passing (3s)
  1 failing

  1) api/v1/auth
       POST /
         should register/create a user:

      AssertionError: expected 404 to equal 200
      + expected - actual

      -404
      +200

      at Context.<anonymous> (tests\user.spec.js:27:35)
      at processTicksAndRejections (internal/process/task_queues.js:97:5)

Test is done with fail. but it is work! So let's make "auth controller" to get expecting result.

Just made "register methods" in auth Controller.

const User = require('../models/User');
const asyncHandler = require('../middlewares/async');

exports.register = asyncHandler(async(req, res, next) => {
  const {name, email, role, password } = req.body;
  
  const user = await User.create({
    name,
    email,
    role,
    password
  });
  res.status(200);
});

Test passed!

wowwwww~

Test Requirement: login(), logout()

- If input valid data(email, password), client gets token.
- If input invalide data(email, password), 400 error occur.

- If request GET /logout, success, and client lose token.(make token to none)

Make test code.

describe("POST /login", async () => {
  it("[Success] login", async () => {
  	const user = {
      name: "test", email: "test@gmail.com", password: "123456", role: "user"
    };
    
    await User.create(user);
    
    const res = await request(app)
        .post("/api/v1/auth/login")
        .send({
        	email: "test@gamil.com",
            password: "123456"
    });
    expect(res.status).to.be.equal(200);
    expect(res.body).to.have.property("token");
  });
  
  it("[Fail] Incorrect email or password", async () => {
      const user = { name: "test",
      				 email: "test@gmail.com", 
                     password: "1234566", 
                     role: "user"
            };

      await User.create(user);
            
      const res = await request(app)
           	.post("/api/v1/auth/login")
            .send({
                email: "test@gmail.com",
                password: "0000000"
      });
      
      expect(res.status).to.be.equal(401);
      expect(res.body.error).to.be.equal('Invalid credentials');
    });
  });
  
describe("GET /logout", async () => {
    it("[Success] logout", async () => {
       const user = {
       	 name: "test", email: "test@gmail.com", password: "1234566", role: "user"
       };
	   await User.create(user);
            
       const res = await request(app)
			.get("/api/v1/auth/logout");
      
       expect(res.status).to.be.equal(200);
       expect(res.body.token).to.be.equal('none');
   });
});    

Then let's make controller methods to pass test.

exports.login = asyncHandler( async (req, res, next) => {
    const { email, password } = req.body;

    // Validate email & password
    if (!email || !password) {
        return next(new ErrorResponse('Please provide an email ans password', 400));
    }

    // Check for user 
    const user = await User.findOne({ email }).select('+password');

    if (!user) {
        return next(new ErrorResponse('Invalid credentials', 401));
    }

    // check if password matches
    const isMatch = await user.matchPassword(password);

    if (!isMatch) {
        return next(new ErrorResponse('Invalid credentials', 401));
    }

    sendTokenResponse(user, 200, res);
});

exports.logout = asyncHandler( async (req, res, next) => {
    res.cookie('token', 'none', {
        expires: new Date(Date.now() + 10 * 1000),
        httpOnly: true
    });

    res.status(200).json({
        success: true,
        token: 'none'
    });
});

matchPassword()

//models.user.js
UserSchema.methods.matchPassword = async function(enteredPassword) {
    return await bcrypt.compare(enteredPassword, this.password);
};

done!

Test Requirement: GET /getMe()

- If client request with valid token, client get self data.

- If client requet with invalid token, 401 error occurs

 


dd

'notAnymore > Udemy API' 카테고리의 다른 글

Course CRUD [TDD, express]  (0) 2020.05.29
Occured Errors [TDD, Express]  (0) 2020.05.25