-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: tests refactoring with fixtures to make them more manageable
Big steps towards #63. Also remove the need to maintain "in memory" repositories and consolidate assertion functions.
- Loading branch information
Showing
108 changed files
with
4,849 additions
and
3,808 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ COPY go.* ./ | |
RUN go mod download | ||
COPY . . | ||
COPY --from=front_builder /app/build ./cmd/serve/front/build | ||
RUN go build -ldflags="-s -w" -o seelf | ||
RUN make build-back | ||
|
||
FROM alpine:3.16 | ||
LABEL org.opencontainers.image.authors="[email protected]" \ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,20 +7,28 @@ serve-docs: # Launch the docs dev server | |
serve-back: # Launch the backend API and creates an admin user if needed | ||
[email protected] ADMIN_PASSWORD=admin LOG_LEVEL=debug go run main.go serve | ||
|
||
test: # Launch every tests | ||
test-front: # Launch the frontend tests | ||
cd cmd/serve/front && npm i && npm test && cd ../../.. | ||
|
||
test-back: # Launch the backend tests | ||
go vet ./... | ||
go test ./... --cover | ||
|
||
test: test-front test-back # Launch every tests | ||
|
||
ts: # Print the current timestamp, useful for migrations | ||
@date +%s | ||
|
||
outdated: # Print direct dependencies and their latest version | ||
go list -v -u -m -f '{{if not .Indirect}}{{.}}{{end}}' all | ||
|
||
build: # Build the final binary for the current platform | ||
build-front: # Build the frontend | ||
cd cmd/serve/front && npm i && npm run build && cd ../../.. | ||
go build -ldflags="-s -w" -o seelf | ||
|
||
build-back: # Build the backend | ||
go build -tags release -ldflags="-s -w" -o seelf | ||
|
||
build: build-front build-back # Build the final binary for the current platform | ||
|
||
build-docs: # Build the docs | ||
npm i && npm run docs:build | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,71 +6,83 @@ import ( | |
|
||
"github.com/YuukanOO/seelf/internal/auth/app/create_first_account" | ||
"github.com/YuukanOO/seelf/internal/auth/domain" | ||
"github.com/YuukanOO/seelf/internal/auth/fixture" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/crypto" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/memory" | ||
"github.com/YuukanOO/seelf/pkg/assert" | ||
"github.com/YuukanOO/seelf/pkg/bus" | ||
"github.com/YuukanOO/seelf/pkg/must" | ||
"github.com/YuukanOO/seelf/pkg/testutil" | ||
"github.com/YuukanOO/seelf/pkg/bus/spy" | ||
"github.com/YuukanOO/seelf/pkg/validate" | ||
) | ||
|
||
func Test_CreateFirstAccount(t *testing.T) { | ||
ctx := context.Background() | ||
hasher := crypto.NewBCryptHasher() | ||
keygen := crypto.NewKeyGenerator() | ||
|
||
sut := func(existingUsers ...*domain.User) bus.RequestHandler[string, create_first_account.Command] { | ||
store := memory.NewUsersStore(existingUsers...) | ||
return create_first_account.Handler(store, store, hasher, keygen) | ||
arrange := func(tb testing.TB, seed ...fixture.SeedBuilder) ( | ||
bus.RequestHandler[string, create_first_account.Command], | ||
spy.Dispatcher, | ||
) { | ||
context := fixture.PrepareDatabase(tb, seed...) | ||
return create_first_account.Handler(context.UsersStore, context.UsersStore, crypto.NewBCryptHasher(), crypto.NewKeyGenerator()), context.Dispatcher | ||
} | ||
|
||
t.Run("should returns the existing user id if a user already exists", func(t *testing.T) { | ||
usr := must.Panic(domain.NewUser(domain.NewEmailRequirement("[email protected]", true), "password", "apikey")) | ||
uc := sut(&usr) | ||
existingUser := fixture.User() | ||
handler, dispatcher := arrange(t, fixture.WithUsers(&existingUser)) | ||
|
||
uid, err := uc(ctx, create_first_account.Command{}) | ||
uid, err := handler(context.Background(), create_first_account.Command{}) | ||
|
||
testutil.IsNil(t, err) | ||
testutil.Equals(t, string(usr.ID()), uid) | ||
assert.Nil(t, err) | ||
assert.Equal(t, string(existingUser.ID()), uid) | ||
assert.HasLength(t, 0, dispatcher.Signals()) | ||
}) | ||
|
||
t.Run("should require both email and password or fail with ErrAdminAccountRequired", func(t *testing.T) { | ||
uc := sut() | ||
uid, err := uc(ctx, create_first_account.Command{}) | ||
handler, _ := arrange(t) | ||
uid, err := handler(context.Background(), create_first_account.Command{}) | ||
|
||
testutil.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
testutil.Equals(t, "", uid) | ||
assert.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
assert.Equal(t, "", uid) | ||
|
||
uid, err = uc(ctx, create_first_account.Command{Email: "[email protected]"}) | ||
testutil.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
testutil.Equals(t, "", uid) | ||
|
||
uid, err = uc(ctx, create_first_account.Command{Password: "admin"}) | ||
testutil.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
testutil.Equals(t, "", uid) | ||
uid, err = handler(context.Background(), create_first_account.Command{Email: "[email protected]"}) | ||
assert.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
assert.Equal(t, "", uid) | ||
|
||
uid, err = handler(context.Background(), create_first_account.Command{Password: "admin"}) | ||
assert.ErrorIs(t, create_first_account.ErrAdminAccountRequired, err) | ||
assert.Equal(t, "", uid) | ||
}) | ||
|
||
t.Run("should require valid inputs", func(t *testing.T) { | ||
uc := sut() | ||
uid, err := uc(ctx, create_first_account.Command{ | ||
Email: "notanemail", | ||
handler, _ := arrange(t) | ||
uid, err := handler(context.Background(), create_first_account.Command{ | ||
Email: "not_an_email", | ||
Password: "admin", | ||
}) | ||
|
||
testutil.ErrorIs(t, validate.ErrValidationFailed, err) | ||
testutil.Equals(t, "", uid) | ||
|
||
assert.Equal(t, "", uid) | ||
assert.ValidationError(t, validate.FieldErrors{ | ||
"email": domain.ErrInvalidEmail, | ||
}, err) | ||
}) | ||
|
||
t.Run("should creates the first user account if everything is good", func(t *testing.T) { | ||
uc := sut() | ||
uid, err := uc(ctx, create_first_account.Command{ | ||
handler, dispatcher := arrange(t) | ||
uid, err := handler(context.Background(), create_first_account.Command{ | ||
Email: "[email protected]", | ||
Password: "admin", | ||
}) | ||
|
||
testutil.IsNil(t, err) | ||
testutil.NotEquals(t, "", uid) | ||
assert.Nil(t, err) | ||
assert.NotEqual(t, "", uid) | ||
|
||
assert.HasLength(t, 1, dispatcher.Signals()) | ||
registered := assert.Is[domain.UserRegistered](t, dispatcher.Signals()[0]) | ||
|
||
assert.Equal(t, domain.UserRegistered{ | ||
ID: domain.UserID(uid), | ||
Email: "[email protected]", | ||
Password: assert.NotZero(t, registered.Password), | ||
RegisteredAt: assert.NotZero(t, registered.RegisteredAt), | ||
Key: assert.NotZero(t, registered.Key), | ||
}, registered) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,66 +6,81 @@ import ( | |
|
||
"github.com/YuukanOO/seelf/internal/auth/app/login" | ||
"github.com/YuukanOO/seelf/internal/auth/domain" | ||
"github.com/YuukanOO/seelf/internal/auth/fixture" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/crypto" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/memory" | ||
"github.com/YuukanOO/seelf/pkg/apperr" | ||
"github.com/YuukanOO/seelf/pkg/assert" | ||
"github.com/YuukanOO/seelf/pkg/bus" | ||
"github.com/YuukanOO/seelf/pkg/must" | ||
"github.com/YuukanOO/seelf/pkg/testutil" | ||
"github.com/YuukanOO/seelf/pkg/bus/spy" | ||
"github.com/YuukanOO/seelf/pkg/validate" | ||
"github.com/YuukanOO/seelf/pkg/validate/strings" | ||
) | ||
|
||
func Test_Login(t *testing.T) { | ||
hasher := crypto.NewBCryptHasher() | ||
password := must.Panic(hasher.Hash("password")) // Sample password hash for the string "password" for tests | ||
existingUser := must.Panic(domain.NewUser(domain.NewEmailRequirement("[email protected]", true), password, "apikey")) | ||
|
||
sut := func(existingUsers ...*domain.User) bus.RequestHandler[string, login.Command] { | ||
store := memory.NewUsersStore(existingUsers...) | ||
return login.Handler(store, hasher) | ||
arrange := func(tb testing.TB, seed ...fixture.SeedBuilder) ( | ||
bus.RequestHandler[string, login.Command], | ||
spy.Dispatcher, | ||
) { | ||
context := fixture.PrepareDatabase(tb, seed...) | ||
return login.Handler(context.UsersStore, hasher), context.Dispatcher | ||
} | ||
|
||
t.Run("should require valid inputs", func(t *testing.T) { | ||
uc := sut() | ||
_, err := uc(context.Background(), login.Command{}) | ||
handler, _ := arrange(t) | ||
_, err := handler(context.Background(), login.Command{}) | ||
|
||
testutil.ErrorIs(t, validate.ErrValidationFailed, err) | ||
assert.ValidationError(t, validate.FieldErrors{ | ||
"email": domain.ErrInvalidEmail, | ||
"password": strings.ErrRequired, | ||
}, err) | ||
}) | ||
|
||
t.Run("should complains if email does not exists", func(t *testing.T) { | ||
uc := sut() | ||
_, err := uc(context.Background(), login.Command{ | ||
handler, _ := arrange(t) | ||
_, err := handler(context.Background(), login.Command{ | ||
Email: "[email protected]", | ||
Password: "nobodycares", | ||
Password: "no_body_cares", | ||
}) | ||
|
||
validationErr, ok := apperr.As[validate.FieldErrors](err) | ||
testutil.IsTrue(t, ok) | ||
testutil.ErrorIs(t, domain.ErrInvalidEmailOrPassword, validationErr["email"]) | ||
testutil.ErrorIs(t, domain.ErrInvalidEmailOrPassword, validationErr["password"]) | ||
assert.ValidationError(t, validate.FieldErrors{ | ||
"email": domain.ErrInvalidEmailOrPassword, | ||
"password": domain.ErrInvalidEmailOrPassword, | ||
}, err) | ||
}) | ||
|
||
t.Run("should complains if password does not match", func(t *testing.T) { | ||
uc := sut(&existingUser) | ||
_, err := uc(context.Background(), login.Command{ | ||
existingUser := fixture.User( | ||
fixture.WithEmail("[email protected]"), | ||
fixture.WithPassword("raw_password_hash", hasher), | ||
) | ||
handler, _ := arrange(t, fixture.WithUsers(&existingUser)) | ||
|
||
_, err := handler(context.Background(), login.Command{ | ||
Email: "[email protected]", | ||
Password: "nobodycares", | ||
Password: "no_body_cares", | ||
}) | ||
|
||
validationErr, ok := apperr.As[validate.FieldErrors](err) | ||
testutil.IsTrue(t, ok) | ||
testutil.ErrorIs(t, domain.ErrInvalidEmailOrPassword, validationErr["email"]) | ||
testutil.ErrorIs(t, domain.ErrInvalidEmailOrPassword, validationErr["password"]) | ||
assert.ValidationError(t, validate.FieldErrors{ | ||
"email": domain.ErrInvalidEmailOrPassword, | ||
"password": domain.ErrInvalidEmailOrPassword, | ||
}, err) | ||
}) | ||
|
||
t.Run("should returns a valid user id if it succeeds", func(t *testing.T) { | ||
uc := sut(&existingUser) | ||
uid, err := uc(context.Background(), login.Command{ | ||
existingUser := fixture.User( | ||
fixture.WithEmail("[email protected]"), | ||
fixture.WithPassword("password", hasher), | ||
) | ||
handler, dispatcher := arrange(t, fixture.WithUsers(&existingUser)) | ||
|
||
uid, err := handler(context.Background(), login.Command{ | ||
Email: "[email protected]", | ||
Password: "password", | ||
}) | ||
|
||
testutil.IsNil(t, err) | ||
testutil.Equals(t, string(existingUser.ID()), uid) | ||
assert.Nil(t, err) | ||
assert.Equal(t, string(existingUser.ID()), uid) | ||
assert.HasLength(t, 0, dispatcher.Signals()) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,43 +6,49 @@ import ( | |
|
||
"github.com/YuukanOO/seelf/internal/auth/app/refresh_api_key" | ||
"github.com/YuukanOO/seelf/internal/auth/domain" | ||
"github.com/YuukanOO/seelf/internal/auth/fixture" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/crypto" | ||
"github.com/YuukanOO/seelf/internal/auth/infra/memory" | ||
"github.com/YuukanOO/seelf/pkg/apperr" | ||
"github.com/YuukanOO/seelf/pkg/assert" | ||
"github.com/YuukanOO/seelf/pkg/bus" | ||
"github.com/YuukanOO/seelf/pkg/must" | ||
"github.com/YuukanOO/seelf/pkg/testutil" | ||
"github.com/YuukanOO/seelf/pkg/bus/spy" | ||
) | ||
|
||
func Test_RefreshApiKey(t *testing.T) { | ||
sut := func(existingUsers ...*domain.User) bus.RequestHandler[string, refresh_api_key.Command] { | ||
store := memory.NewUsersStore(existingUsers...) | ||
|
||
return refresh_api_key.Handler(store, store, crypto.NewKeyGenerator()) | ||
arrange := func(tb testing.TB, seed ...fixture.SeedBuilder) ( | ||
bus.RequestHandler[string, refresh_api_key.Command], | ||
spy.Dispatcher, | ||
) { | ||
context := fixture.PrepareDatabase(tb, seed...) | ||
return refresh_api_key.Handler(context.UsersStore, context.UsersStore, crypto.NewKeyGenerator()), context.Dispatcher | ||
} | ||
|
||
t.Run("should fail if the user does not exists", func(t *testing.T) { | ||
uc := sut() | ||
handler, _ := arrange(t) | ||
|
||
_, err := uc(context.Background(), refresh_api_key.Command{}) | ||
_, err := handler(context.Background(), refresh_api_key.Command{}) | ||
|
||
testutil.ErrorIs(t, apperr.ErrNotFound, err) | ||
assert.ErrorIs(t, apperr.ErrNotFound, err) | ||
}) | ||
|
||
t.Run("should refresh the user's API key if everything is good", func(t *testing.T) { | ||
user := must.Panic(domain.NewUser(domain.NewEmailRequirement("[email protected]", true), "someHashedPassword", "apikey")) | ||
uc := sut(&user) | ||
existingUser := fixture.User() | ||
handler, dispatcher := arrange(t, fixture.WithUsers(&existingUser)) | ||
|
||
key, err := uc(context.Background(), refresh_api_key.Command{ | ||
ID: string(user.ID())}, | ||
key, err := handler(context.Background(), refresh_api_key.Command{ | ||
ID: string(existingUser.ID())}, | ||
) | ||
|
||
testutil.IsNil(t, err) | ||
testutil.NotEquals(t, "", key) | ||
assert.Nil(t, err) | ||
assert.NotEqual(t, "", key) | ||
|
||
evt := testutil.EventIs[domain.UserAPIKeyChanged](t, &user, 1) | ||
assert.HasLength(t, 1, dispatcher.Signals()) | ||
keyChanged := assert.Is[domain.UserAPIKeyChanged](t, dispatcher.Signals()[0]) | ||
|
||
testutil.Equals(t, user.ID(), evt.ID) | ||
testutil.Equals(t, key, string(evt.Key)) | ||
assert.Equal(t, domain.UserAPIKeyChanged{ | ||
ID: existingUser.ID(), | ||
Key: domain.APIKey(key), | ||
}, keyChanged) | ||
}) | ||
} |
Oops, something went wrong.