From ca2985abb4fa380c29fc4f6a9d1873bb7463cc39 Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Sun, 13 Oct 2024 21:10:33 +0200 Subject: [PATCH] feat: add session create --- .../howmuch/adapter/controller/session.go | 80 +++++++++++++++- .../adapter/controller/session_test.go | 95 +++++++++++++++++++ .../controller/usecasemock/testuserusecase.go | 55 +++++++++++ internal/howmuch/registry/session.go | 32 ++++++- internal/pkg/token/token.go | 2 +- 5 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 internal/howmuch/adapter/controller/session_test.go create mode 100644 internal/howmuch/adapter/controller/usecasemock/testuserusecase.go diff --git a/internal/howmuch/adapter/controller/session.go b/internal/howmuch/adapter/controller/session.go index 5ea18c0..7392e01 100644 --- a/internal/howmuch/adapter/controller/session.go +++ b/internal/howmuch/adapter/controller/session.go @@ -1,7 +1,34 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package controller import ( + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/core" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/token" "github.com/gin-gonic/gin" ) @@ -9,12 +36,55 @@ type Session interface { Create(*gin.Context) } -type SessionController struct{} - -func NewSessionController() Session { - return &SessionController{} +type SessionController struct { + userUsecase usecase.User } +func NewSessionController(u usecase.User) Session { + return &SessionController{u} +} + +type Token struct { + Token string `json:"token"` +} + +type createParams struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` +} + +// Create creates a session for a user and returns a token +// +// Since we use JWT method, this token is not stored anywhere. Thus it +// stops at the controller level. func (sc *SessionController) Create(ctx *gin.Context) { - log.CtxLog(ctx).DebugLog("session create") + var params createParams + + if err := ctx.Bind(¶ms); err != nil { + log.ErrorLog("param error", "err", err) + core.WriteResponse(ctx, UserParamsErr, nil) + return + } + + user := model.User{ + Email: params.Email, + Password: params.Password, + } + + err := sc.userUsecase.Exist(ctx, &user) + if err != nil { + core.WriteResponse(ctx, err, nil) + return + } + + // user exists. Generate the token for the user + tokenString, err := token.Sign(user.Email) + if err != nil { + core.WriteResponse(ctx, errno.InternalServerErr, nil) + return + } + + core.WriteResponse(ctx, nil, Token{ + Token: tokenString, + }) } diff --git a/internal/howmuch/adapter/controller/session_test.go b/internal/howmuch/adapter/controller/session_test.go new file mode 100644 index 0000000..564ebca --- /dev/null +++ b/internal/howmuch/adapter/controller/session_test.go @@ -0,0 +1,95 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package controller + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + "time" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller/usecasemock" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/test" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/token" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestSessionCreate(t *testing.T) { + tests := []struct { + Name string + User createParams + Errno *errno.Errno + }{ + {"registered user", createParams{ + Email: "correct@correct.com", + Password: "strong password", + }, errno.OK}, + {"unregistered user", createParams{ + Email: "unregistered@error.com", + Password: "strong password", + }, usecase.UserNotExist}, + } + + token.Init("secret", 1*time.Second) + + for _, tst := range tests { + t.Run(tst.Name, func(t *testing.T) { + testUserUsecase := usecasemock.NewtestUserUsecase() + sessionController := NewSessionController(testUserUsecase) + r := gin.New() + r.POST( + "/session/create", + func(ctx *gin.Context) { sessionController.Create(ctx) }, + ) + user, _ := json.Marshal(tst.User) + res := test.PerformRequest(t, r, "POST", "/session/create", bytes.NewReader(user), + test.Header{ + Key: "content-type", + Value: "application/json", + }) + + assert.Equal(t, tst.Errno.HTTP, res.Result().StatusCode, res.Body) + + if tst.Errno.HTTP != http.StatusOK { + var got errno.Errno + err := json.NewDecoder(res.Result().Body).Decode(&got) + // XXX: the http status is not in the json. So it must be reset back the the struct + got.HTTP = res.Result().StatusCode + assert.NoError(t, err) + assert.Equal(t, tst.Errno, &got) + return + } + + var got Token + err := json.NewDecoder(res.Result().Body).Decode(&got) + assert.NoError(t, err) + tkResp, err := token.Parse(got.Token) + assert.NoError(t, err) + assert.Equal(t, tst.User.Email, tkResp.Identity) + }) + } +} diff --git a/internal/howmuch/adapter/controller/usecasemock/testuserusecase.go b/internal/howmuch/adapter/controller/usecasemock/testuserusecase.go new file mode 100644 index 0000000..ffa0ddf --- /dev/null +++ b/internal/howmuch/adapter/controller/usecasemock/testuserusecase.go @@ -0,0 +1,55 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package usecasemock + +import ( + "context" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase" +) + +type testUserUsecase struct{} + +func NewtestUserUsecase() usecase.User { + return &testUserUsecase{} +} + +func (*testUserUsecase) Create(ctx context.Context, u *model.User) (*model.User, error) { + return nil, nil +} + +func (*testUserUsecase) Exist(ctx context.Context, u *model.User) error { + switch u.Email { + case "a@b.c": + if u.Password == "strong password" { + return nil + } else { + return usecase.UserWrongPassword + } + case "unregistered@error.com": + return usecase.UserNotExist + } + // Should never reach here + return nil +} diff --git a/internal/howmuch/registry/session.go b/internal/howmuch/registry/session.go index c9c3a14..4e10f2b 100644 --- a/internal/howmuch/registry/session.go +++ b/internal/howmuch/registry/session.go @@ -1,9 +1,35 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package registry -import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller" +import ( + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/usecase" +) // NewSessionController returns a session controller's implementation func (r *registry) NewSessionController() controller.Session { - // u := usecase.NewSessionUsecase(repo.NewSessionRepository(r.db), repo.NewDBRepository(r.db)) - return controller.NewSessionController() + u := usecase.NewUserUsecase(repo.NewUserRepository(r.db), repo.NewDBRepository(r.db)) + return controller.NewSessionController(u) } diff --git a/internal/pkg/token/token.go b/internal/pkg/token/token.go index ecc47da..eb97516 100644 --- a/internal/pkg/token/token.go +++ b/internal/pkg/token/token.go @@ -49,7 +49,7 @@ type TokenResp struct { var ( once sync.Once - config *Config + config Config ) var ErrMissingHeader = errors.New("Authorization is needed in the header")