From 7b8abf8e5c2a945adfc777b1dd29a2edafe2a3b3 Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Sun, 6 Oct 2024 16:18:04 +0200 Subject: [PATCH] feat: Create user usecase --- README.md | 39 +++++++- internal/howmuch/adapter/repo/db.go | 68 ++++++++++++++ internal/howmuch/adapter/repo/user.go | 91 +++++++++++++++++++ internal/howmuch/model/.keep | 0 internal/howmuch/model/user.go | 36 ++++++++ internal/howmuch/registry/registry.go | 6 +- internal/howmuch/usecase/biz/.keep | 0 internal/howmuch/usecase/repo/.keep | 0 internal/howmuch/usecase/repo/db.go | 32 +++++++ internal/howmuch/usecase/repo/testdb.go | 34 +++++++ internal/howmuch/usecase/repo/testuser.go | 42 +++++++++ internal/howmuch/usecase/repo/user.go | 33 +++++++ internal/howmuch/usecase/usecase/user.go | 78 ++++++++++++++++ internal/howmuch/usecase/usecase/user_test.go | 49 ++++++++++ 14 files changed, 504 insertions(+), 4 deletions(-) create mode 100644 internal/howmuch/adapter/repo/db.go create mode 100644 internal/howmuch/adapter/repo/user.go delete mode 100644 internal/howmuch/model/.keep create mode 100644 internal/howmuch/model/user.go delete mode 100644 internal/howmuch/usecase/biz/.keep delete mode 100644 internal/howmuch/usecase/repo/.keep create mode 100644 internal/howmuch/usecase/repo/db.go create mode 100644 internal/howmuch/usecase/repo/testdb.go create mode 100644 internal/howmuch/usecase/repo/testuser.go create mode 100644 internal/howmuch/usecase/repo/user.go create mode 100644 internal/howmuch/usecase/usecase/user.go create mode 100644 internal/howmuch/usecase/usecase/user_test.go diff --git a/README.md b/README.md index b481c89..2a2fd19 100644 --- a/README.md +++ b/README.md @@ -198,9 +198,46 @@ type User struct { Use Buffalo pop `Soda CLI` to create database migrations. -### 2024/10/07 +### 2024/10/06 Implement the architecture design for User entity. Checked out OpenAPI, and found that it was not that simple at all. It needs a whole package of knowledge about the web development! + +For the test-driven part, + +- model layer: just model designs, **nothing to test** +- infra: routes and db connections, it works when it works. Nothing to test. +- registry: Just return some structs, no logic. **Not worth testing** +- adapter: + - input-port (controller) test: it is about testing parsing the input + value, and the output results writing. The unit test of controller is to + **make sure that they behave as defined in the API documentation**. To + test, we have to mock the **business service**. + - output-port (repo) test: it is about testing converting business model + to database model and the interaction with the database. If we are going + to test them, it's about simulating different type of database behaviour + (success, timeout, etc.). To test, we have to mock the + **database connection**. +- usecase: This is the core part to test, it's about the core business. +We provide the data input and we check the data output in a fake repository. + +With this design, although it may seem overkill for this little project, fits +perfectly well with the TDD method. + +Concretely, I will do the TDD for my usecase level development, and for the +rest, I just put unit tests aside for later. + +#### Workflow + +1. OAS Definition +2. (Integration/Validation test) +3. Usecase unit test cases +4. Usecase development +5. Refactor (2-3-4) +6. Input-port/Output-port + +That should be the correct workflow. But to save time, I will cut off the +integration test part (the 2nd point). + diff --git a/internal/howmuch/adapter/repo/db.go b/internal/howmuch/adapter/repo/db.go new file mode 100644 index 0000000..8214510 --- /dev/null +++ b/internal/howmuch/adapter/repo/db.go @@ -0,0 +1,68 @@ +// 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 repo + +import ( + "context" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" + "github.com/jackc/pgx/v5" +) + +type dbRepository struct { + db *pgx.Conn +} + +func NewDBRepository(db *pgx.Conn) repo.DBRepository { + return &dbRepository{ + db: db, + } +} + +// XXX: Do I need rollback? in which cases? + +func (dr *dbRepository) Transaction( + ctx context.Context, + txFunc func(txCtx context.Context, tx interface{}) (interface{}, error), +) (interface{}, error) { + tx, err := dr.db.Begin(ctx) + if err != nil { + return nil, err + } + + defer func() { + if p := recover(); p != nil { + tx.Rollback(ctx) + log.PanicLog("transaction panicked!") + } else if err != nil { + tx.Rollback(ctx) + log.ErrorLog("transaction failed!", "err", err) + } else { + err = tx.Commit(ctx) + } + }() + + data, err := txFunc(ctx, tx) + return data, err +} diff --git a/internal/howmuch/adapter/repo/user.go b/internal/howmuch/adapter/repo/user.go new file mode 100644 index 0000000..df23842 --- /dev/null +++ b/internal/howmuch/adapter/repo/user.go @@ -0,0 +1,91 @@ +// 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 repo + +import ( + "context" + "errors" + "time" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo/sqlc" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" +) + +// Get business service model, convert to the DB model (generated by sqlc) +// To test repo's methods, I have to mock a pgx.Conn + +type userRepository struct { + db *pgx.Conn +} + +const insertTimeout = 1 * time.Second + +func NewUserRepository(db *pgx.Conn) repo.UserRepository { + return &userRepository{ + db: db, + } +} + +// Create +func (ur *userRepository) Create( + ctx context.Context, + transaction interface{}, + u *model.User, +) (*model.User, error) { + timeoutCtx, cancel := context.WithTimeout(ctx, insertTimeout) + defer cancel() + + args := sqlc.InsertUserParams{ + Email: u.Email, + FirstName: u.FirstName, + LastName: u.LastName, + Password: u.Password, + CreatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + UpdatedAt: pgtype.Timestamp{Time: time.Now(), Valid: true}, + } + + tx, ok := transaction.(*pgx.Conn) + if !ok { + return nil, errors.New("transaction is not a *pgx.Conn") + } + + queries := sqlc.New(tx) + + userDB, err := queries.InsertUser(timeoutCtx, args) + if err != nil { + return nil, err + } + + return &model.User{ + ID: int(userDB.ID), + Email: userDB.Email, + FirstName: userDB.FirstName, + LastName: userDB.LastName, + Password: userDB.Password, + CreatedAt: userDB.CreatedAt.Time, + UpdatedAt: userDB.CreatedAt.Time, + }, nil +} diff --git a/internal/howmuch/model/.keep b/internal/howmuch/model/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/internal/howmuch/model/user.go b/internal/howmuch/model/user.go new file mode 100644 index 0000000..f9af487 --- /dev/null +++ b/internal/howmuch/model/user.go @@ -0,0 +1,36 @@ +// 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 model + +import "time" + +// User model +type User struct { + ID int `json:"id"` + Email string `json:"email"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Password string `json:"password"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/internal/howmuch/registry/registry.go b/internal/howmuch/registry/registry.go index 33dc30a..137627d 100644 --- a/internal/howmuch/registry/registry.go +++ b/internal/howmuch/registry/registry.go @@ -24,7 +24,7 @@ package registry import ( "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller" - "github.com/jackc/pgx/v5/pgxpool" + "github.com/jackc/pgx/v5" ) // registry is an implementation of Registry interface. @@ -32,7 +32,7 @@ import ( // It might holds other drivers when the projects grows. For example // the object needed to connect to Redis or Kafka. type registry struct { - db *pgxpool.Pool + db *pgx.Conn } // Registry returns a new app controller that will be used by main()/run() @@ -43,7 +43,7 @@ type Registry interface { } // NewRegistry returns a new Registry's implementation. -func NewRegistry(db *pgxpool.Pool) Registry { +func NewRegistry(db *pgx.Conn) Registry { return ®istry{db: db} } diff --git a/internal/howmuch/usecase/biz/.keep b/internal/howmuch/usecase/biz/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/internal/howmuch/usecase/repo/.keep b/internal/howmuch/usecase/repo/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/internal/howmuch/usecase/repo/db.go b/internal/howmuch/usecase/repo/db.go new file mode 100644 index 0000000..42d9a89 --- /dev/null +++ b/internal/howmuch/usecase/repo/db.go @@ -0,0 +1,32 @@ +// 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 repo + +import "context" + +type DBRepository interface { + Transaction( + ctx context.Context, + txFunc func(txCtx context.Context, tx interface{}) (interface{}, error), + ) (interface{}, error) +} diff --git a/internal/howmuch/usecase/repo/testdb.go b/internal/howmuch/usecase/repo/testdb.go new file mode 100644 index 0000000..756f662 --- /dev/null +++ b/internal/howmuch/usecase/repo/testdb.go @@ -0,0 +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 repo + +import "context" + +type TestDBRepository struct{} + +func (tdr *TestDBRepository) Transaction( + ctx context.Context, + txFunc func(txCtx context.Context, tx interface{}) (interface{}, error), +) (interface{}, error) { + return txFunc(ctx, nil) +} diff --git a/internal/howmuch/usecase/repo/testuser.go b/internal/howmuch/usecase/repo/testuser.go new file mode 100644 index 0000000..574b5e1 --- /dev/null +++ b/internal/howmuch/usecase/repo/testuser.go @@ -0,0 +1,42 @@ +// 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 repo + +import ( + "context" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" +) + +type TestUserRepository struct{} + +func (tur *TestUserRepository) Create( + ctx context.Context, + transaction interface{}, + u *model.User, +) (*model.User, error) { + user := *u + + user.ID = 123 + return &user, nil +} diff --git a/internal/howmuch/usecase/repo/user.go b/internal/howmuch/usecase/repo/user.go new file mode 100644 index 0000000..ea2abf0 --- /dev/null +++ b/internal/howmuch/usecase/repo/user.go @@ -0,0 +1,33 @@ +// 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 repo + +import ( + "context" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" +) + +type UserRepository interface { + Create(ctx context.Context, transaction interface{}, u *model.User) (*model.User, error) +} diff --git a/internal/howmuch/usecase/usecase/user.go b/internal/howmuch/usecase/usecase/user.go new file mode 100644 index 0000000..ca5c46f --- /dev/null +++ b/internal/howmuch/usecase/usecase/user.go @@ -0,0 +1,78 @@ +// 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 usecase + +import ( + "context" + "fmt" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo" + "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" +) + +type userUsecase struct { + userRepo repo.UserRepository + dbRepo repo.DBRepository +} + +type User interface { + Create(ctx context.Context, u *model.User) (*model.User, error) +} + +func NewUserUsecase(r repo.UserRepository, d repo.DBRepository) User { + return &userUsecase{ + userRepo: r, + dbRepo: d, + } +} + +func (uuc *userUsecase) Create(ctx context.Context, u *model.User) (*model.User, error) { + data, err := uuc.dbRepo.Transaction( + ctx, + func(txCtx context.Context, tx interface{}) (interface{}, error) { + u, err := uuc.userRepo.Create(txCtx, tx, u) + if err != nil { + return nil, err + } + + // TODO: App log, maybe can be sent to some third party service. + log.InfoLog( + "created new user", + "email", + u.Email, + "name", + fmt.Sprintf("%s %s", u.FirstName, u.LastName), + ) + + return u, err + }, + ) + user := data.(*model.User) + + if err != nil { + return nil, err + } + + return user, nil +} diff --git a/internal/howmuch/usecase/usecase/user_test.go b/internal/howmuch/usecase/usecase/user_test.go new file mode 100644 index 0000000..b9d5f7f --- /dev/null +++ b/internal/howmuch/usecase/usecase/user_test.go @@ -0,0 +1,49 @@ +// 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 usecase + +import ( + "context" + "testing" + + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo" + "github.com/stretchr/testify/assert" +) + +func TestCreateUser(t *testing.T) { + ctx := context.Background() + userUsecase := NewUserUsecase(&repo.TestUserRepository{}, &repo.TestDBRepository{}) + input := &model.User{ + Email: "a@b.c", + FirstName: "James", + LastName: "Bond", + Password: "verystrong", + } + want := input + want.ID = 123 + + got, err := userUsecase.Create(ctx, input) + assert.NoError(t, err) + assert.Equal(t, want, got) +}