이전 글
이제 남은 것은 핸들러, 메인 함수 구현과 통합 테스트를 작성해 보는 것이다.
통합 테스트는 포스트맨으로 직접 두드려서 하거나
포스트맨으로 테스트 컬렉션을 만들어 돌려도 되지만
그 이전에 한 번 더 검증한다는 느낌으로 코드를 작성해 보았다.
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 를 이용한 인증 구현이 끝이 났다.
'Programming > Go' 카테고리의 다른 글
[GO 인증 구현 with JWT 4] User Repository, Service 구현 및 테스트 (0) | 2022.04.13 |
---|---|
[GO 인증 구현 with JWT 3] 인증 미들웨어 구현 및 테스트 코드 작성 (0) | 2022.04.11 |
[GO 인증 구현 with JWT 2] 토큰 구현 및 테스트 코드 작성 (0) | 2022.04.11 |
[GO 인증 구현 with JWT 1] 전체적인 인터페이스 작성 (0) | 2022.04.09 |
Go 로 MinIO 에 파일 업다운로드 (0) | 2022.04.06 |
댓글