14 - 로그인 프로세서 추가
42161 단어 gingotestingauthentication
그림에서 보듯이 우리는
handler
층 로그인 방법부터 시작하여 다음에 모든 다른 층의 상세한 정보를 갱신할 것이다. 그 중 일부 기능은 우리가 이미 실현했다!여느 때와 마찬가지로 본 강좌의 모든 코드를 사용하여 서명repo on Github하고 각 과목의 한 부분을 포함합니다!
만약 당신이 동영상을 좋아한다면, 아래의 동영상 버전을 보십시오!
사용자 서비스 인터페이스에 로그인 추가
사용자가 로그인할 때, 우리는 사용자가 등록할 때와 같이 사용자의 전자메일과 비밀번호를 받아들이기를 희망합니다.로그인과 등록의 실현 세부 사항은 다르지만 방법 서명은 같다.
Signin
에 UserService
인터페이스에 ~/model/interfaces.go
를 추가합니다.Signin
와 마찬가지로, 우리는 부분적으로 채워진 *model.User
을 전달하고, 로그인에 실패하면 오류를 되돌려줍니다.로그인에 성공하면 이 방법에 전달된 사용자는 모든 사용자의 상세한 정보를 포함하는 것으로 수정됩니다.// UserService defines methods the handler layer expects
// any service it interacts with to implement
type UserService interface {
Get(ctx context.Context, uid uuid.UUID) (*User, error)
Signup(ctx context.Context, u *User) error
Signin(ctx context.Context, u *User) error
}
이 업데이트는 DellmockUserService
과 서비스 층UserService
을 파괴할 것입니다. 왜냐하면 이 방법이 실현되지 않았기 때문입니다.Signup
에 ~/service/user_service.go
의 불완전한 실현을 추가하면 다음 강좌에서 완성할 것입니다.// Signin reaches our to a UserRepository check if the user exists
// and then compares the supplied password with the provided password
// if a valid email/password combo is provided, u will hold all
// available user fields
func (s *userService) Signin(ctx context.Context, u *model.User) error {
panic("Not implemented")
}
아날로그 서명 방법 추가
프로세서 논리와 단원 테스트를 추가하기 전에
~/model/mocks/user_service.go
방법으로 저희Signin
를 업데이트하겠습니다.더 많은 정보는 이전의 테스트와 테스트 강좌testify를 참조하세요!func (m *MockUserService) Signin(ctx context.Context, u *model.User) error {
ret := m.Called(ctx, u)
var r0 error
if ret.Get(0) != nil {
r0 = ret.Get(0).(error)
}
return r0
}
프로세서 추가
우리는 이 처리 프로그램에 대량의 코드를 추가할 것이기 때문에
Signin
처리 프로그램을 ~/handler/handler.go
에서 자신의 파일~/handler/signin.go
로 복사할 것이다.이 방법의 내용도 다음과 같이 업데이트할 것입니다.package handler
// IMPORTS OMITTED
// signinReq is not exported
type signinReq struct {
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,gte=6,lte=30"`
}
// Signin used to authenticate extant user
func (h *Handler) Signin(c *gin.Context) {
var req signinReq
if ok := bindData(c, &req); !ok {
return
}
u := &model.User{
Email: req.Email,
Password: req.Password,
}
ctx := c.Request.Context()
err := h.UserService.Signin(ctx, u)
if err != nil {
log.Printf("Failed to sign in user: %v\n", err.Error())
c.JSON(apperrors.Status(err), gin.H{
"error": err,
})
return
}
tokens, err := h.TokenService.NewPairFromUser(ctx, u, "")
if err != nil {
log.Printf("Failed to create tokens for user: %v\n", err.Error())
c.JSON(apperrors.Status(err), gin.H{
"error": err,
})
return
}
c.JSON(http.StatusOK, gin.H{
"tokens": tokens,
})
}
우리는 이 처리 프로그램 방법에서 다음과 같은 조작을 실행한다.signinReq
함수를 사용하여 데이터를 bind_data
에 연결합니다.데이터가 바인딩되지 않거나 검증에 실패하면 이 함수는 오류 요청 HTTP status 400 오류를 보냅니다.실제로 bind_data
를 위해 단독 테스트를 만드는 것은 현명하다(사실이 증명하듯이 본 강좌에 결함이 존재한다)😂). *model.User
를 만들 것입니다.c.Request.Context()
gin 상하문에서 요청 상하문을 추출합니다.UserService.Signin
방법 및 오류 처리만약 오류가 발생하면, 우리는 apperrors.Status()
함수를 사용하여 이 오류가 사용자 정의 apperrors
에 정의된 오류에 속하는지 확인합니다.테스트 신호
이 테스트를 위한 새 파일
~/handler/signin_test.go
을 만듭니다.이 파일에서 우리는 다음과 같은 내용을 테스트할 것이다.TokenService.NewPairFromUser
(이것은 처리 프로그램 결과가 성공했다는 것을 의미한다).바디 및 설정 테스트
우리는 테스트의 설정 부분 (
TokenService.NewPairFromUser
블록 앞에서 우리의 아날로그 서비스,gin 엔진/공유기, 처리 프로그램을 실례화할 것입니다.그러나 설정에서 우리의 아날로그 방법 응답을 정의하는 것이 아니라 단일
t.Run
블록에서 mock.On(...)
으로 정의하기로 결정했습니다.내가 이렇게 한 것은 최종적으로 설정에서 정의와 변수를 호출하는 시뮬레이션 방법을 너무 많이 만들어서 어떤 변수가 어떤 테스트 용례에 대응하는지 이해하기 어려웠기 때문이다.package handler
// IMPORTS OMITTED
func TestSignin(t *testing.T) {
// Setup
gin.SetMode(gin.TestMode)
// setup mock services, gin engine/router, handler layer
mockUserService := new(mocks.MockUserService)
mockTokenService := new(mocks.MockTokenService)
router := gin.Default()
NewHandler(&Config{
R: router,
UserService: mockUserService,
TokenService: mockTokenService,
})
// Tests will be added here below
// ...
}
잘못된 요청 데이터 사례
이 예에서는 잘못된 전자 우편 주소가 있는 JSON 요청 주체를 만들고 이를 저희 프로세서에 보냈습니다. 테스트 설정 부분에서 이 프로세서를 실례화했습니다.우리는 HTTP 상태 400
t.Run
을 보낼 것이며 http.StatusBadRequest
와 mockUserService.Signin
방법을 호출하지 않을 것이라고 단언했다. 왜냐하면 처리 프로그램은 이 방법에 도달하기 전에 되돌아와야 하기 때문이다. t.Run("Bad request data", func(t *testing.T) {
// a response recorder for getting written http response
rr := httptest.NewRecorder()
// create a request body with invalid fields
reqBody, err := json.Marshal(gin.H{
"email": "notanemail",
"password": "short",
})
assert.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
assert.NoError(t, err)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rr, request)
assert.Equal(t, http.StatusBadRequest, rr.Code)
mockUserService.AssertNotCalled(t, "Signin")
mockTokenService.AssertNotCalled(t, "NewTokensFromUser")
})
사용자 서비스 오류.Signin 사례
이 예에서, 우리는
mockTokenService.NewPairFromUser
에 아날로그 응답을 정의했는데, 이것은 오류를 되돌려줍니다.우리는 이러한 오류의 예시를 만들어 Signin
에 저장했다.그런 다음 HTTP-674라고 부르고 status-454라고 부르며 HTTP-674라고 부릅니다. t.Run("Error Returned from UserService.Signin", func(t *testing.T) {
email := "[email protected]"
password := "pwdoesnotmatch123"
mockUSArgs := mock.Arguments{
mock.AnythingOfType("*context.emptyCtx"),
&model.User{Email: email, Password: password},
}
// so we can check for a known status code
mockError := apperrors.NewAuthorization("invalid email/password combo")
mockUserService.On("Signin", mockUSArgs...).Return(mockError)
// a response recorder for getting written http response
rr := httptest.NewRecorder()
// create a request body with valid fields
reqBody, err := json.Marshal(gin.H{
"email": email,
"password": password,
})
assert.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
assert.NoError(t, err)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rr, request)
mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
mockTokenService.AssertNotCalled(t, "NewTokensFromUser")
assert.Equal(t, http.StatusUnauthorized, rr.Code)
})
성공 사례
이 예에서 우리는
mockError
에 대한 응답에 오류가 없다(http.StatusUnauthorized
.우리는 또한 mockUserService.Signin
에서 온 유효한 영패 응답을 시뮬레이션했다.우리는 상술한 두 가지 방법을 호출했다고 단언하고 HTTP 상태가 200mockTokenService.NewPairFromUser
인 유효한 HTTP 응답을 되돌렸다. t.Run("Successful Token Creation", func(t *testing.T) {
email := "[email protected]"
password := "pwworksgreat123"
mockUSArgs := mock.Arguments{
mock.AnythingOfType("*context.emptyCtx"),
&model.User{Email: email, Password: password},
}
mockUserService.On("Signin", mockUSArgs...).Return(nil)
mockTSArgs := mock.Arguments{
mock.AnythingOfType("*context.emptyCtx"),
&model.User{Email: email, Password: password},
"",
}
mockTokenPair := &model.TokenPair{
IDToken: "idToken",
RefreshToken: "refreshToken",
}
mockTokenService.On("NewPairFromUser", mockTSArgs...).Return(mockTokenPair, nil)
// a response recorder for getting written http response
rr := httptest.NewRecorder()
// create a request body with valid fields
reqBody, err := json.Marshal(gin.H{
"email": email,
"password": password,
})
assert.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
assert.NoError(t, err)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rr, request)
respBody, err := json.Marshal(gin.H{
"tokens": mockTokenPair,
})
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, respBody, rr.Body.Bytes())
mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
mockTokenService.AssertCalled(t, "NewPairFromUser", mockTSArgs...)
})
토큰 서비스 오류.NewPairFromUser 사례
본 예에서 우리는
mockUserService.Signin
방법을 성공적으로 호출하기를 희망하지만 nil
에 오류가 발생하기를 희망한다.그리고 우리는 HTTP 500mockTokenService.NewPairFromUser
을 응답으로 기대합니다. t.Run("Failed Token Creation", func(t *testing.T) {
email := "[email protected]"
password := "cannotproducetoken"
mockUSArgs := mock.Arguments{
mock.AnythingOfType("*context.emptyCtx"),
&model.User{Email: email, Password: password},
}
mockUserService.On("Signin", mockUSArgs...).Return(nil)
mockTSArgs := mock.Arguments{
mock.AnythingOfType("*context.emptyCtx"),
&model.User{Email: email, Password: password},
"",
}
mockError := apperrors.NewInternal()
mockTokenService.On("NewPairFromUser", mockTSArgs...).Return(nil, mockError)
// a response recorder for getting written http response
rr := httptest.NewRecorder()
// create a request body with valid fields
reqBody, err := json.Marshal(gin.H{
"email": email,
"password": password,
})
assert.NoError(t, err)
request, err := http.NewRequest(http.MethodPost, "/signin", bytes.NewBuffer(reqBody))
assert.NoError(t, err)
request.Header.Set("Content-Type", "application/json")
router.ServeHTTP(rr, request)
respBody, err := json.Marshal(gin.H{
"error": mockError,
})
assert.NoError(t, err)
assert.Equal(t, mockError.Status(), rr.Code)
assert.Equal(t, respBody, rr.Body.Bytes())
mockUserService.AssertCalled(t, "Signin", mockUSArgs...)
mockTokenService.AssertCalled(t, "NewPairFromUser", mockTSArgs...)
})
결론
오늘은 이게 다야, 키코스!다음에 우리는
http.StatusOK
의 구체적인 실현과 데이터베이스에서 사용자를 얻고 비밀번호를 입력하는 데 필요한 mockUserService.Signin
방법을 작성할 것이다.'주, 하스타 루에고!
Reference
이 문제에 관하여(14 - 로그인 프로세서 추가), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/jacobsngoodwin/14-add-signin-handler-32be텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)