프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [Node/Rest API] SFA - (2) - 컨트롤러와 라우터 - 회원가입

    2020. 11. 14. 03:42

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    컨트롤러와 라우터 작성방법

    컨트롤러는 다음과 같이 밸리데이션 모듈을 이용하여 어떤 파라미터에 대해서 체크를 할 것인지를 정한 후, 마지막으로 async 블록으로 함수를 선언하여  해당하는 요청에 대한 로직 및 예외처리를 작성합니다.

     

    export const controllerFunction1 = [
      validate.json,
      validateHandler,
      async (req: Request, res: Response)  => { ... }
    ]

     

    이어서 라우터에 매핑하기 위해서는 아래와 같이 작성할 수 있습니다.

     

    import express from "express";
    import {
      controllerFunction1
    } from "./user.controller";
    
    const sampleRouter = express.Router();
    
    sampleRouter
    .route("/controllerFunction1")
    .post(controllerFunction1);
    
    export default sampleRouter;

     

    이와같은 방법으로 인증에 관한 컨트롤러를 작성해 보겠습니다.

     


    회원가입 컨트롤러 작성하기

    컨트롤러를 작성하기 이전에 성공과 실패의 경우에 대해서 다음과 같이 정의할 필요가 있습니다. 다음과 같이 우선 정리를 해 두면 테스트코드를 작성하기에 훨씬 수월하기 때문입니다.

     

    성공 시

    201 - 계정생성 완료

     

    실패 시

    400 - 이미 로그인된 상태 

    409 - 이미 존재하는 이메일 입력

    500 - 입력된 데이터가 데이터베이스에 저장되지 않은 경우

    500 - 서버 에러 (여러가지 요인이 존재함)

     

    자! 그러면 위에 정의된 코드를 기준으로 테스트코드를 작성해 보겠습니다. 원래는 테스트코드를 작성하기 위해서는 훨씬 효율적인 방법이 있다고 생각하지만, 저는 아직 실무경험이 부족하기 때문에 투박하게 작성해보겠습니다.

     

    저는 총 4개의 단계로 나누어서 작성을 합니다.

    - Data 영역에서는 데이터베이스에 등록하거나 수정하거나 할 객체를 미리 선언해 둡니다.

    - Request 영역에서는 애플리케이션에 해당하는 요청을 보내고 응답 객체를 리턴받습니다.

    - Result 영역에서는 테스트 관련 함수를 통해 응답받은 객체에에 대해서 예상된 결과가 나왔는지를 검증합니다.

    - Reset 영역에서는 테스트에서 사용된 데이터를 지워줍니다.

     

    describe("POST - /signup", function () {
      describe("성공 시", function(){
        it("201 - 계정 생성 완료", async function () {
          // Data
          const data = { email: 'signup@rollback.com', password: 'Test1234!' };
    
          // Request
          const response = await server.post("/oauth2/signup").withCredentials().send(data);
    
          // Result
          const user = await User.findOne({ email: data.email }).then(e => e);
          expect(user).not.null;
          expect(response.status).to.equal(201);
    
          // Reset
          await User.deleteOne({ email: data.email });
        });
      })
      describe("실패 시", function(){
        it("400 - 이미 로그인된 상태", async function(){
          // Data
          const data = { email: 'signup1@rollback.com', password: 'Test1234!' };
          const newData = { email: 'signup2@rollback.com', password: 'Test1234!' };
    
          // Request
          await server.post("/oauth2/signup").withCredentials().send(data);
          await server.post("/oauth2/signin").withCredentials().send(data);
          const response = await server.post("/oauth2/signup").withCredentials().send(newData).then(e=>e);
    
          // Result
          expect(response.status).to.equal(400);
    
          // Reset
          await server.get("/oauth2/signout").withCredentials();
          await User.deleteMany({ email: { $in: [ data.email, newData.email ] } });
        })
        it("409 - 이미 존재하는 이메일", async function () {
          // Data
          const data = { email: 'signup1@rollback.com', password: 'Test1234!' };
    
          // Request
          await server.post("/oauth2/signup").withCredentials().send(data);
          const response = await server.post("/oauth2/signup").withCredentials().send(data);
    
          // Result
          expect(response.status).to.equal(409);
    
          // Reset
          await User.deleteOne({ email: data.email });
        });
        it("400 - 이메일이 형식에 맞지 않음", async function () {
          // Data
          const data = { email: 'signup1rollback.com', password: 'Test1234!' };
    
          // Request
          const response = await server.post("/oauth2/signup").withCredentials().send(data);
    
          // Status
          expect(response.status).to.equal(400);
        });
        it("400 - 비밀번호에 대문자, 소문자, 숫자, 특수문자가 1개 이상 존재하지 않음", async function () {
          // Data
          const data = { email: 'signup1@rollback.com', password: 'test1234!' };
    
          // Request
          const response = await server.post("/oauth2/signup").withCredentials().send(data);
    
          // Status
          expect(response.status).to.equal(400);
        });
      })
    });

     

    테스트코드를 기반으로 원하는 결과가 나올 수 있도록 다음과 같이 로직을 작성해줍니다.

     

    export const signUp = [
      validate.json,
      validate.email,
      validate.password,
      validateHandler,
      async (req: Request, res: Response)  => {
        // Request Body Parameter
        const { email, password, ...rest } = req.body;
    
        try {
          // Check - refresh_token 값은 로그인 성공시 쿠키로 저장되는 값입니다. 로그아웃시 지워집니다.
          const refresh_token = req.signedCookies.refresh_token
          if (refresh_token) {
            console.log('BAD : 이미 로그인된 상태라서 로그아웃 이후 가입해주세요.', refresh_token);
            return res.status(400).send(MESSAGE.ALREADY_LOGGED_IN);
          }
    
          // Check - user 데이터를 MongoDB에서 불러온 값이 있다면, 이미 있는 계정이라고 볼 수 있습니다.
          let user = await User.findOne({ email });
          if (user) {
            console.log('BAD : 이미 존재하는 이메일을 입력했습니다.', email);
            return res.status(409).send(MESSAGE.ALREADY_USER);
          }
    
          /**
           * Task
           * - 세션 아이디 생성 (세션 중복 로그인, 세션 유지시간)
           * - 데이터베이스에 입력한 회원 데이터 저장
           * - 회원가입 확인 메일 전송
           */
          let session_string = randomBytes(32).toString("hex");
          let newUser = new User({ email, password, session_string, ...rest });
          try {
            await newUser.save();
            await sendSignUpConfirmationEmail(email, res);
            res.status(201).send(MESSAGE.CONFIRM_EMAIL);
          } catch (err) {
            console.error('ERROR : 입력된 데이터가 데이터베이스에 저장되지 않았습니다.', err)
            res.status(500).send(MESSAGE.TRY_AGAIN);
          }
          
        } catch (err) {
          console.error('ERROR : 서버의 상태를 체크해주세요', err);
          return res.status(500).send(MESSAGE.TRY_AGAIN); // 그 외에 에러나면 재요청 바람
        }
      }
    ];