@nakedev/go-scaffold 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +598 -306
- package/dist/commands/auth.js +65 -23
- package/dist/commands/check.js +281 -0
- package/dist/commands/config.js +50 -0
- package/dist/commands/create.js +33 -2
- package/dist/commands/generate.js +29 -3
- package/dist/commands/method.js +74 -63
- package/dist/commands/migration.js +2 -2
- package/dist/commands/observability.js +4 -53
- package/dist/commands/rbac.js +21 -10
- package/dist/commands/undo.js +11 -3
- package/dist/commands/worker.js +15 -5
- package/dist/index.js +198 -59
- package/dist/prompts/auth-wizard.js +40 -6
- package/dist/prompts/create-wizard.js +42 -1
- package/dist/prompts/generate-wizard.js +89 -9
- package/dist/templates/auth-manifest.js +50 -19
- package/dist/templates/create-manifest.js +8 -0
- package/dist/templates/module-manifest.js +84 -26
- package/dist/templates/rbac-manifest.js +16 -11
- package/dist/templates/worker-manifest.js +4 -1
- package/dist/types.js +8 -0
- package/dist/utils/auth-patcher.js +124 -33
- package/dist/utils/config.js +167 -4
- package/dist/utils/docs-patcher.js +68 -0
- package/dist/utils/hexagonal-method-patcher.js +334 -0
- package/dist/utils/main-patcher.js +32 -30
- package/dist/utils/marker-patch.js +7 -1
- package/dist/utils/module-location.js +17 -11
- package/dist/utils/module-profile.js +32 -0
- package/dist/utils/platform-patcher.js +56 -7
- package/dist/utils/rbac-patcher.js +89 -210
- package/package.json +7 -2
- package/templates/add/auth/cmd/seed/main.go.hbs +15 -3
- package/templates/add/auth/docs/login.yaml.hbs +11 -1
- package/templates/add/auth/docs/mfa-verify.yaml.hbs +19 -0
- package/templates/add/auth/docs/provider-exchange.yaml.hbs +40 -0
- package/templates/add/auth/docs/provider-login.yaml.hbs +31 -0
- package/templates/add/auth/docs/refresh.yaml.hbs +7 -0
- package/templates/add/auth/docs/register.yaml.hbs +7 -0
- package/templates/add/auth/docs/reset-password.yaml.hbs +1 -1
- package/templates/add/auth/docs/schemas.yaml.hbs +59 -1
- package/templates/add/auth/docs/users-me-mfa-confirm.yaml.hbs +19 -0
- package/templates/add/auth/docs/users-me-mfa-disable.yaml.hbs +15 -0
- package/templates/add/auth/docs/users-me-mfa-setup.yaml.hbs +14 -0
- package/templates/add/auth/docs/users-me-mfa.yaml.hbs +12 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/browser_policy.go.hbs +98 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/dto.go.hbs +159 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_local.go.hbs +76 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_mfa.go.hbs +83 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_oauth.go.hbs +70 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_recovery.go.hbs +49 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_test.go.hbs +311 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/handler_user.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/adapters/inbound/http/session_cookie.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt.go.hbs +35 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/password/bcrypt_test.go.hbs +20 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store.go.hbs +129 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/mfa_store_test.go.hbs +174 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/model.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/repository.go.hbs +211 -0
- package/templates/add/auth/internal/app/user/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +18 -19
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg.go.hbs +213 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_pg_test.go.hbs +103 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/postgres/tokenstore_recovery.go.hbs +84 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore.go.hbs +228 -0
- package/templates/add/auth/internal/app/user/adapters/outbound/redis/tokenstore_test.go.hbs +196 -0
- package/templates/add/auth/internal/app/user/application/contracts.go.hbs +52 -0
- package/templates/add/auth/internal/app/user/application/dto.go.hbs +75 -0
- package/templates/add/auth/internal/app/user/application/errors.go.hbs +62 -0
- package/templates/add/auth/internal/app/user/application/external_login.go.hbs +198 -0
- package/templates/add/auth/internal/app/user/application/jwt.go.hbs +58 -0
- package/templates/add/auth/internal/app/user/application/local_auth.go.hbs +96 -0
- package/templates/add/auth/internal/app/user/application/mfa_service.go.hbs +449 -0
- package/templates/add/auth/internal/app/user/application/mfa_service_test.go.hbs +200 -0
- package/templates/add/auth/internal/app/user/application/oauth.go.hbs +132 -0
- package/templates/add/auth/internal/app/user/application/provider_test.go.hbs +285 -0
- package/templates/add/auth/internal/app/user/application/recovery.go.hbs +82 -0
- package/templates/add/auth/internal/app/user/application/recovery_service.go.hbs +112 -0
- package/templates/add/auth/internal/app/user/application/service.go.hbs +145 -0
- package/templates/add/auth/internal/app/user/application/service_test.go.hbs +891 -0
- package/templates/add/auth/internal/app/user/application/sessions.go.hbs +99 -0
- package/templates/add/auth/internal/app/user/application/tokenstore_ports.go.hbs +14 -0
- package/templates/add/auth/internal/app/user/application/user_query.go.hbs +65 -0
- package/templates/add/auth/internal/app/user/composition.go.hbs +168 -0
- package/templates/add/auth/internal/app/user/domain/entity.go.hbs +41 -0
- package/templates/add/auth/internal/app/user/domain/errors.go.hbs +32 -0
- package/templates/add/auth/internal/app/user/ports/password.go.hbs +9 -0
- package/templates/add/auth/internal/app/user/ports/repository.go.hbs +90 -0
- package/templates/add/auth/internal/platform/authprovider/google/google.go.hbs +389 -0
- package/templates/add/auth/internal/platform/authprovider/google/google_test.go.hbs +312 -0
- package/templates/add/auth/migrations/create_auth_tokens.up.sql.hbs +10 -5
- package/templates/add/auth/migrations/create_identities.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_login_throttle.up.sql.hbs +1 -1
- package/templates/add/auth/migrations/create_mfa.down.sql.hbs +3 -0
- package/templates/add/auth/migrations/create_mfa.up.sql.hbs +29 -0
- package/templates/add/auth/migrations/create_users.up.sql.hbs +4 -3
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler.go.hbs +142 -0
- package/templates/add/rbac/internal/app/role/adapters/inbound/http/handler_test.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/model.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/adapters/outbound/postgres/repository.go.hbs +127 -0
- package/templates/add/rbac/internal/app/role/{repository_test.go.hbs → adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/add/rbac/internal/app/role/application/dto.go.hbs +47 -0
- package/templates/add/rbac/internal/app/role/application/errors.go.hbs +19 -0
- package/templates/add/rbac/internal/app/role/application/service.go.hbs +157 -0
- package/templates/add/rbac/internal/app/role/{service_test.go.hbs → application/service_test.go.hbs} +26 -19
- package/templates/add/rbac/internal/app/role/composition.go.hbs +48 -0
- package/templates/add/rbac/internal/app/role/domain/entity.go.hbs +23 -0
- package/templates/add/rbac/internal/app/role/domain/errors.go.hbs +26 -0
- package/templates/add/rbac/internal/app/role/ports/repository.go.hbs +25 -0
- package/templates/add/rbac/migrations/add_roles.down.sql.hbs +3 -11
- package/templates/add/rbac/migrations/add_roles.up.sql.hbs +17 -6
- package/templates/add/worker/internal/platform/queue/river_test.go.hbs +84 -0
- package/templates/create/base/.claude/skills/go-scaffold/SKILL.md.hbs +358 -121
- package/templates/create/base/.env.example.hbs +0 -1
- package/templates/create/base/.golangci.yml.hbs +2 -2
- package/templates/create/base/AGENTS.md.hbs +279 -67
- package/templates/create/base/Makefile.hbs +2 -1
- package/templates/create/base/README.md.hbs +115 -32
- package/templates/create/base/cmd/api/wiring.go.hbs +13 -9
- package/templates/create/base/internal/composition/doc.go.hbs +7 -0
- package/templates/create/base/internal/platform/database/database.go.hbs +3 -3
- package/templates/create/base/internal/shared/apperror/apperror.go.hbs +15 -2
- package/templates/create/base/internal/shared/config/config.go.hbs +0 -8
- package/templates/create/base/internal/shared/middleware/cors_test.go.hbs +40 -0
- package/templates/create/base/internal/shared/middleware/error.go.hbs +15 -5
- package/templates/create/features/docs/architecture.md.hbs +92 -32
- package/templates/create/features/docs/patterns.md.hbs +137 -91
- package/templates/create/features/docs/techstack.md.hbs +18 -3
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.go.hbs +45 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/dto.minimal.go.hbs +28 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.go.hbs +182 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler.minimal.go.hbs +83 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_crud_test.go.hbs +18 -0
- package/templates/generate/module/hexagonal/adapters/inbound/http/handler_test.go.hbs +30 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/model.go.hbs +37 -0
- package/templates/generate/module/hexagonal/adapters/outbound/postgres/repository.go.hbs +95 -0
- package/templates/generate/module/{repository_test.go.hbs → hexagonal/adapters/outbound/postgres/repository_test.go.hbs} +8 -8
- package/templates/generate/module/hexagonal/application/commands.crud.go.hbs +54 -0
- package/templates/generate/module/hexagonal/application/commands.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/cqrs_test.go.hbs +66 -0
- package/templates/generate/module/hexagonal/application/dto.go.hbs +35 -0
- package/templates/generate/module/hexagonal/application/dto.minimal.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/queries.crud.go.hbs +33 -0
- package/templates/generate/module/hexagonal/application/queries.go.hbs +25 -0
- package/templates/generate/module/hexagonal/application/service.crud.go.hbs +73 -0
- package/templates/generate/module/hexagonal/application/service.go.hbs +29 -0
- package/templates/generate/module/hexagonal/application/service_test.go.hbs +62 -0
- package/templates/generate/module/hexagonal/composition.go.hbs +27 -0
- package/templates/generate/module/hexagonal/domain/entity.go.hbs +20 -0
- package/templates/generate/module/hexagonal/domain/errors.go.hbs +11 -0
- package/templates/generate/module/hexagonal/ports/repository.go.hbs +38 -0
- package/templates/generate/module/migration.up.sql.hbs +1 -1
- package/dist/utils/method-patcher.js +0 -357
- package/templates/add/auth/docs/google-callback.yaml.hbs +0 -22
- package/templates/add/auth/docs/google-login.yaml.hbs +0 -7
- package/templates/add/auth/internal/app/user/dto.go.hbs +0 -77
- package/templates/add/auth/internal/app/user/errors.go.hbs +0 -43
- package/templates/add/auth/internal/app/user/handler.go.hbs +0 -276
- package/templates/add/auth/internal/app/user/jwt.go.hbs +0 -108
- package/templates/add/auth/internal/app/user/model/authtoken.go.hbs +0 -39
- package/templates/add/auth/internal/app/user/model/identity.go.hbs +0 -31
- package/templates/add/auth/internal/app/user/model/loginthrottle.go.hbs +0 -26
- package/templates/add/auth/internal/app/user/model/user.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/repository.go.hbs +0 -137
- package/templates/add/auth/internal/app/user/service.go.hbs +0 -531
- package/templates/add/auth/internal/app/user/service_test.go.hbs +0 -316
- package/templates/add/auth/internal/app/user/tokenstore.go.hbs +0 -30
- package/templates/add/auth/internal/app/user/tokenstore_pg.go.hbs +0 -144
- package/templates/add/auth/internal/app/user/tokenstore_redis.go.hbs +0 -147
- package/templates/add/rbac/internal/app/role/dto.go.hbs +0 -45
- package/templates/add/rbac/internal/app/role/errors.go.hbs +0 -39
- package/templates/add/rbac/internal/app/role/handler.go.hbs +0 -104
- package/templates/add/rbac/internal/app/role/model/permission.go.hbs +0 -12
- package/templates/add/rbac/internal/app/role/model/role.go.hbs +0 -22
- package/templates/add/rbac/internal/app/role/model/role_permission.go.hbs +0 -11
- package/templates/add/rbac/internal/app/role/repository.go.hbs +0 -97
- package/templates/add/rbac/internal/app/role/service.go.hbs +0 -217
- package/templates/generate/module/dto.go.hbs +0 -36
- package/templates/generate/module/errors.go.hbs +0 -33
- package/templates/generate/module/handler.go.hbs +0 -134
- package/templates/generate/module/handler_test.go.hbs +0 -174
- package/templates/generate/module/minimal/dto.go.hbs +0 -28
- package/templates/generate/module/minimal/handler.go.hbs +0 -48
- package/templates/generate/module/minimal/handler_test.go.hbs +0 -10
- package/templates/generate/module/minimal/service.go.hbs +0 -45
- package/templates/generate/module/minimal/service_test.go.hbs +0 -77
- package/templates/generate/module/model/model.go.hbs +0 -36
- package/templates/generate/module/repository.go.hbs +0 -103
- package/templates/generate/module/service.go.hbs +0 -108
- package/templates/generate/module/service_test.go.hbs +0 -161
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"errors"
|
|
6
|
-
|
|
7
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
8
|
-
"{{goModule}}/internal/shared/apperror"
|
|
9
|
-
"{{goModule}}/internal/shared/dberr"
|
|
10
|
-
"{{goModule}}/internal/shared/id"
|
|
11
|
-
|
|
12
|
-
"github.com/google/uuid"
|
|
13
|
-
"gorm.io/gorm"
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
// repository = what the service needs from the data layer (declared on the consumer side, so it can be mocked in tests)
|
|
17
|
-
type repository interface {
|
|
18
|
-
Create(ctx context.Context, m *model.{{pascalName}}) error
|
|
19
|
-
FindAll(ctx context.Context, limit, offset int) ([]model.{{pascalName}}, error)
|
|
20
|
-
FindByID(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error)
|
|
21
|
-
Update(ctx context.Context, m *model.{{pascalName}}) error
|
|
22
|
-
Delete(ctx context.Context, id uuid.UUID) error
|
|
23
|
-
// go-scaffold:repository-interface
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Service = business logic for {{pkg}} (rules live here, it knows nothing about HTTP)
|
|
27
|
-
// input shape validation happens in the handler (binding tags)
|
|
28
|
-
type Service struct {
|
|
29
|
-
repo repository
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func NewService(repo repository) *Service {
|
|
33
|
-
return &Service{repo: repo}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
func (s *Service) Create(ctx context.Context, in createInput) (*model.{{pascalName}}, error) {
|
|
37
|
-
// TODO: set real fields from in
|
|
38
|
-
_ = in
|
|
39
|
-
m := &model.{{pascalName}}{ID: id.New()}
|
|
40
|
-
if err := s.repo.Create(ctx, m); err != nil {
|
|
41
|
-
if dberr.IsDuplicate(err) {
|
|
42
|
-
return nil, errConflict()
|
|
43
|
-
}
|
|
44
|
-
return nil, apperror.NewInternal()
|
|
45
|
-
}
|
|
46
|
-
return m, nil
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
func (s *Service) List(ctx context.Context, limit, offset int) ([]model.{{pascalName}}, error) {
|
|
50
|
-
items, err := s.repo.FindAll(ctx, limit, offset)
|
|
51
|
-
if err != nil {
|
|
52
|
-
return nil, apperror.NewInternal()
|
|
53
|
-
}
|
|
54
|
-
return items, nil
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
func (s *Service) Get(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error) {
|
|
58
|
-
m, err := s.repo.FindByID(ctx, id)
|
|
59
|
-
if err != nil {
|
|
60
|
-
return nil, wrapFindErr(err)
|
|
61
|
-
}
|
|
62
|
-
return m, nil
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
func (s *Service) Update(ctx context.Context, id uuid.UUID, in updateInput) (*model.{{pascalName}}, error) {
|
|
66
|
-
m, err := s.repo.FindByID(ctx, id)
|
|
67
|
-
if err != nil {
|
|
68
|
-
return nil, wrapFindErr(err)
|
|
69
|
-
}
|
|
70
|
-
// TODO: apply real fields from in before saving
|
|
71
|
-
// The version the client read comes from the request, not from the row we
|
|
72
|
-
// just loaded — comparing the row against itself would always succeed and
|
|
73
|
-
// defeat the check.
|
|
74
|
-
m.Version = in.Version
|
|
75
|
-
if err := s.repo.Update(ctx, m); err != nil {
|
|
76
|
-
if errors.Is(err, ErrStaleVersion) {
|
|
77
|
-
return nil, errStale()
|
|
78
|
-
}
|
|
79
|
-
if dberr.IsDuplicate(err) {
|
|
80
|
-
return nil, errConflict()
|
|
81
|
-
}
|
|
82
|
-
return nil, apperror.NewInternal()
|
|
83
|
-
}
|
|
84
|
-
return m, nil
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
func (s *Service) Delete(ctx context.Context, id uuid.UUID) error {
|
|
88
|
-
if err := s.repo.Delete(ctx, id); err != nil {
|
|
89
|
-
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
90
|
-
return errNotFound()
|
|
91
|
-
}
|
|
92
|
-
// deleting a {{pkg}} that's still referenced elsewhere → FK RESTRICT trips = 409, not 500
|
|
93
|
-
if dberr.IsForeignKey(err) {
|
|
94
|
-
return errHasReferences()
|
|
95
|
-
}
|
|
96
|
-
return apperror.NewInternal()
|
|
97
|
-
}
|
|
98
|
-
return nil
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
func wrapFindErr(err error) error {
|
|
102
|
-
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
103
|
-
return errNotFound()
|
|
104
|
-
}
|
|
105
|
-
return apperror.NewInternal()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// go-scaffold:service-methods
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
package {{pkg}}
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"errors"
|
|
6
|
-
"testing"
|
|
7
|
-
|
|
8
|
-
"{{goModule}}/internal/app/{{modulePath}}/model"
|
|
9
|
-
"{{goModule}}/internal/shared/apperror"
|
|
10
|
-
|
|
11
|
-
"github.com/google/uuid"
|
|
12
|
-
"gorm.io/gorm"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
// repositoryStub exposes one function per dependency operation. Tests configure
|
|
16
|
-
// only the calls they expect; an unexpected call panics instead of silently
|
|
17
|
-
// returning a shared zero value/error that can hide broken orchestration.
|
|
18
|
-
type repositoryStub struct {
|
|
19
|
-
createFn func(context.Context, *model.{{pascalName}}) error
|
|
20
|
-
findAllFn func(context.Context, int, int) ([]model.{{pascalName}}, error)
|
|
21
|
-
findByIDFn func(context.Context, uuid.UUID) (*model.{{pascalName}}, error)
|
|
22
|
-
updateFn func(context.Context, *model.{{pascalName}}) error
|
|
23
|
-
deleteFn func(context.Context, uuid.UUID) error
|
|
24
|
-
// go-scaffold:repository-stub-fields
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
func (s *repositoryStub) Create(ctx context.Context, m *model.{{pascalName}}) error {
|
|
28
|
-
if s.createFn == nil {
|
|
29
|
-
panic("unexpected repository.Create call")
|
|
30
|
-
}
|
|
31
|
-
return s.createFn(ctx, m)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
func (s *repositoryStub) FindAll(ctx context.Context, limit, offset int) ([]model.{{pascalName}}, error) {
|
|
35
|
-
if s.findAllFn == nil {
|
|
36
|
-
panic("unexpected repository.FindAll call")
|
|
37
|
-
}
|
|
38
|
-
return s.findAllFn(ctx, limit, offset)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func (s *repositoryStub) FindByID(ctx context.Context, id uuid.UUID) (*model.{{pascalName}}, error) {
|
|
42
|
-
if s.findByIDFn == nil {
|
|
43
|
-
panic("unexpected repository.FindByID call")
|
|
44
|
-
}
|
|
45
|
-
return s.findByIDFn(ctx, id)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
func (s *repositoryStub) Update(ctx context.Context, m *model.{{pascalName}}) error {
|
|
49
|
-
if s.updateFn == nil {
|
|
50
|
-
panic("unexpected repository.Update call")
|
|
51
|
-
}
|
|
52
|
-
return s.updateFn(ctx, m)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
func (s *repositoryStub) Delete(ctx context.Context, id uuid.UUID) error {
|
|
56
|
-
if s.deleteFn == nil {
|
|
57
|
-
panic("unexpected repository.Delete call")
|
|
58
|
-
}
|
|
59
|
-
return s.deleteFn(ctx, id)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// go-scaffold:repository-stub-methods
|
|
63
|
-
|
|
64
|
-
func status(t *testing.T, err error) int {
|
|
65
|
-
t.Helper()
|
|
66
|
-
var appErr *apperror.AppError
|
|
67
|
-
if !errors.As(err, &appErr) {
|
|
68
|
-
t.Fatalf("expected *apperror.AppError, got %T: %v", err, err)
|
|
69
|
-
}
|
|
70
|
-
return appErr.HTTPStatus
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
func TestService_Create_Duplicate(t *testing.T) {
|
|
74
|
-
repo := &repositoryStub{
|
|
75
|
-
createFn: func(context.Context, *model.{{pascalName}}) error {
|
|
76
|
-
return gorm.ErrDuplicatedKey
|
|
77
|
-
},
|
|
78
|
-
}
|
|
79
|
-
svc := NewService(repo)
|
|
80
|
-
|
|
81
|
-
_, err := svc.Create(context.Background(), createInput{})
|
|
82
|
-
|
|
83
|
-
if got := status(t, err); got != 409 {
|
|
84
|
-
t.Fatalf("want 409 conflict, got %d", got)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
func TestService_Get_NotFound(t *testing.T) {
|
|
89
|
-
repo := &repositoryStub{
|
|
90
|
-
findByIDFn: func(context.Context, uuid.UUID) (*model.{{pascalName}}, error) {
|
|
91
|
-
return nil, gorm.ErrRecordNotFound
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
svc := NewService(repo)
|
|
95
|
-
|
|
96
|
-
_, err := svc.Get(context.Background(), uuid.New())
|
|
97
|
-
|
|
98
|
-
if got := status(t, err); got != 404 {
|
|
99
|
-
t.Fatalf("want 404, got %d", got)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
func TestService_List_DBError_Becomes500(t *testing.T) {
|
|
104
|
-
repo := &repositoryStub{
|
|
105
|
-
findAllFn: func(context.Context, int, int) ([]model.{{pascalName}}, error) {
|
|
106
|
-
return nil, errors.New("connection refused")
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
svc := NewService(repo)
|
|
110
|
-
|
|
111
|
-
_, err := svc.List(context.Background(), 20, 0)
|
|
112
|
-
|
|
113
|
-
if got := status(t, err); got != 500 {
|
|
114
|
-
t.Fatalf("want 500, got %d", got)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// The check that stops one user's save from silently erasing another's: the
|
|
119
|
-
// repository reports the row moved on, the client must see 409 rather than a
|
|
120
|
-
// 500 or, worse, a success.
|
|
121
|
-
func TestService_Update_StaleVersion(t *testing.T) {
|
|
122
|
-
repo := &repositoryStub{
|
|
123
|
-
findByIDFn: func(_ context.Context, id uuid.UUID) (*model.{{pascalName}}, error) {
|
|
124
|
-
return &model.{{pascalName}}{ID: id, Version: 7}, nil
|
|
125
|
-
},
|
|
126
|
-
updateFn: func(context.Context, *model.{{pascalName}}) error {
|
|
127
|
-
return ErrStaleVersion
|
|
128
|
-
},
|
|
129
|
-
}
|
|
130
|
-
svc := NewService(repo)
|
|
131
|
-
|
|
132
|
-
_, err := svc.Update(context.Background(), uuid.New(), updateInput{Version: 3})
|
|
133
|
-
|
|
134
|
-
if got := status(t, err); got != 409 {
|
|
135
|
-
t.Fatalf("want 409 stale, got %d", got)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// The version compared against the database has to be the one the client sent,
|
|
140
|
-
// not the one just read back — comparing the row against itself would always
|
|
141
|
-
// match and the guard would never fire.
|
|
142
|
-
func TestService_Update_SendsClientVersion(t *testing.T) {
|
|
143
|
-
var seen int
|
|
144
|
-
repo := &repositoryStub{
|
|
145
|
-
findByIDFn: func(_ context.Context, id uuid.UUID) (*model.{{pascalName}}, error) {
|
|
146
|
-
return &model.{{pascalName}}{ID: id, Version: 7}, nil
|
|
147
|
-
},
|
|
148
|
-
updateFn: func(_ context.Context, m *model.{{pascalName}}) error {
|
|
149
|
-
seen = m.Version
|
|
150
|
-
return nil
|
|
151
|
-
},
|
|
152
|
-
}
|
|
153
|
-
svc := NewService(repo)
|
|
154
|
-
|
|
155
|
-
if _, err := svc.Update(context.Background(), uuid.New(), updateInput{Version: 3}); err != nil {
|
|
156
|
-
t.Fatalf("update: %v", err)
|
|
157
|
-
}
|
|
158
|
-
if seen != 3 {
|
|
159
|
-
t.Fatalf("want the client's version 3 to reach the repository, got %d", seen)
|
|
160
|
-
}
|
|
161
|
-
}
|