본문 바로가기
Programming/Go

[GO 인증 구현 with JWT 5] Handler, Main 구현 및 통합 테스트

by TinKerBellBass 2022. 4. 15.
728x90
반응형

이전 글

 

[GO 인증 구현 with JWT 4] User Repository, Service 구현 및 테스트

이전 글 [GO 인증 구현 with JWT 3] 인증 미들웨어 구현 및 테스트 코드 작성 이전 글 [GO 인증 구현 with JWT 1] 전체적인 인터페이스 작성 Use Case 간단히 구현해 볼 인증의 시나리오 유저 생성 → 생성된

tinkerbellbass.tistory.com

 

이제 남은 것은 핸들러, 메인 함수 구현과 통합 테스트를 작성해 보는 것이다.

통합 테스트는 포스트맨으로 직접 두드려서 하거나

포스트맨으로 테스트 컬렉션을 만들어 돌려도 되지만

그 이전에 한 번 더 검증한다는 느낌으로 코드를 작성해 보았다.

 

User Handler 구현

핸들러는 딱히 어딘가에 의존성 주입을 할 일이 없기 때문에 인터페이스를 만들지는 않았고,

테스트 등 외부에서 사용할 수 있게 public 구조체로 구현하였다.

리퀘스트를 받아서 서비스에 처리를 위임하고 결과를 받아서 응답하는

유저 등록(SignUp), 등록 정보를 바탕으로 토큰 발급(SignIn), 토큰 정보를 바탕으로 유저 정보를 획득하는(GetSelfUser)

세 개의 메서드를 가지는 핸들러이다.

GetSelfUser 는 메인 함수에서 인증 미들웨어를 삽입해서 토큰 검증을 하고 User ID 값을 컨텍스트에서 받아오고 있다.

type UserHandler struct {
	userService UserServiceIF
}

func NewUserHandler(userService UserServiceIF) *UserHandler {
	return &UserHandler{userService: userService}
}

func (u *UserHandler) SignUp(w http.ResponseWriter, r *http.Request) {
	var userDto UserDto
	err := json.NewDecoder(r.Body).Decode(&userDto)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
		return
	}

	userResponse, err := u.userService.SignUp(userDto)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
		return
	}

	w.WriteHeader(http.StatusCreated)
	err = json.NewEncoder(w).Encode(userResponse)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
	}
}

func (u *UserHandler) SignIn(w http.ResponseWriter, r *http.Request) {
	var userDto UserDto
	err := json.NewDecoder(r.Body).Decode(&userDto)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
		return
	}

	token, err := u.userService.SignIn(userDto)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
		return
	}

	w.WriteHeader(http.StatusCreated)
	_, err = fmt.Fprintf(w, "{\"token\":\"%s\"}", token)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
	}
}

func (u *UserHandler) GetSelfUser(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	userID, ok := ctx.Value("user_id").(string)
	if !ok {
		http.Error(w, "unauthorized user", http.StatusUnauthorized)
		return
	}

	userResponse, err := u.userService.GetUserByID(userID)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
		return
	}

	w.WriteHeader(http.StatusOK)
	err = json.NewEncoder(w).Encode(userResponse)
	if err != nil {
		http.Error(w, err.Error(), authentication.ErrStatusCode(err))
	}
}

 

Main 구현

고 프로그램의 진입점인 메인함수 구현을 마지막으로 코드 작성은 끝이 난다.

먼저 필요한 인스턴스를 생성하고 의존성 주입을 통해 엮어준 후

라우터에서 엔드포인트와 핸들러를 엮어준다.

그리고 토큰 인증 테스트를 위한 토큰을 바탕으로 자신의 유정 정보를 획득하는 요청 앞에

토큰 인증 미들웨어를 삽입하고 있다.

func main() {
	r := chi.NewRouter()
	r.Use(middleware.Logger)

	// TODO 환경변수에서 값을 얻어오는 등의 보안적인 조치를 취할 것
	secret := "secret"
	authentication := auth.NewAuthentication(secret)
	userRepository := users.NewUserRepository()
	userService := users.NewUserService(userRepository, secret)
	userHandler := users.NewUserHandler(userService)

	r.Route("/users", func(r chi.Router) {
		// 등록
		r.Post("/", userHandler.SignUp)

		// 등록한 유저 대상으로 토큰 발급
		r.Post("/token", userHandler.SignIn)

		// 토큰 인증 테스트
		r.Route("/who_am_i", func(r chi.Router) {
			r.Use(authentication.StripTokenMiddleware)
			r.Get("/", userHandler.GetSelfUser)
		})
	})

	err := http.ListenAndServe(":3333", r)
	if err != nil {
		panic(err)
	}
}

 

통합 테스트 작성

모든 시나리오에 대한 테스트 코드를 작성하기는 솔직히... 귀찮아서... 

성공 케이스 하나만 작성해 보았다.

테스트에 필요한 함수나 데이터는 생략하였고, 깃헙에서 확인할 수 있다.

테스트한 성공 시나리오는 

'유저 등록 성공 -> 등록 정보를 바탕으로 토큰 발급 성공 -> 발급된 토큰으로 유정 정보 조회 성공'이다.

request 와 response 는 httptest 패키지를 이용하였다.

기본 언어 레벨에서 http 테스트를 위한 라이브러리를 제공하는 게 마음에 드는 GO 이다.

func (i *IntegrationTestSuite) TestSuccess() {
	i.T().Run("유저 등록 성공 -> 등록 정보로 토큰 발급 성공 -> 토큰으로 유저 정보 조회 성공", func(t *testing.T) {
		userDto := &users.UserDto{
			UserID:   "testUser",
			Password: "testPassword",
		}
		// 유저 등록 **************************************************************************************
		r, err := createRequest("POST", "/users", userDto, nil)
		if err != nil {
			t.Error(err)
		}
		w := httptest.NewRecorder()
		// 실행
		signUpHandler := http.HandlerFunc(i.handler.SignUp)
		signUpHandler.ServeHTTP(w, r)
		// 검증
		assert.Equal(t, http.StatusCreated, w.Result().StatusCode)
		userResponse, err := getUserResponseFromResponse(w.Result())
		if err != nil {
			t.Error(err)
		}
		assert.Equal(t, userDto.UserID, userResponse.UserID)

		// 등록 정보로 토큰 발급 ***************************************************************************
		r, err = createRequest("POST", "/users/token", userDto, nil)
		if err != nil {
			t.Error(err)
		}
		w = httptest.NewRecorder()
		signInHandler := http.HandlerFunc(i.handler.SignIn)
		// 실행
		signInHandler.ServeHTTP(w, r)
		// 검증
		assert.Equal(t, http.StatusCreated, w.Result().StatusCode)
		token, err := getTokenFromResponse(w.Result())
		if err != nil {
			t.Error(err)
		}
		assert.NotNil(t, token)
		assert.NotEmpty(t, token)

		// 토큰으로 유저 정보 조회 ************************************************************************
		headerMap := map[string]string{
			"Authorization": "Bearer " + token,
		}

		r, err = createRequest("GET", "/users/whon_am_i", nil, headerMap)
		if err != nil {
			t.Error(err)
		}
		w = httptest.NewRecorder()
		getSelfUserHandler := http.HandlerFunc(i.handler.GetSelfUser)
		authentication := auth.NewAuthentication(secret)
		stripTokenMiddleWare := authentication.StripTokenMiddleware(getSelfUserHandler)
		// 실행
		stripTokenMiddleWare.ServeHTTP(w, r)
		// 검증
		assert.Equal(t, http.StatusOK, w.Result().StatusCode)
		userResponse, err = getUserResponseFromResponse(w.Result())
		if err != nil {
			t.Error(err)
		}
		assert.Equal(t, userDto.UserID, userResponse.UserID)
	})
}

 

이상으로 GO 를 이용한 인증 구현이 끝이 났다.

전세 소스는 https://github.com/Jongwon-Hyun/go_auth_jwt

728x90
반응형

댓글