feat: more role backend and admin/writer roles additions and adjustments

This commit is contained in:
Florian Sylvain committed 2026-10-26 18:15:26 +01:00
1 parent 8c6c6456da
commit de142f6c5f
131 files changed
+2663 -435

No files matched your search

+28 -10
View File
@@ -86,7 +86,7 @@ func newUserRolesFixture(t *testing.T, maxAttempts int) *userRolesFixture {
r.Use(middleware.RequireVerifiedUser(getUser))
r.Get("/", inviteHandler.ListUsers)
r.Group(func(r chi.Router) {
r.Use(middleware.RequireOwner(getUser))
r.Use(middleware.RequirePermission(getUser, useCases.NewCheckPermissionUseCase(newSeededMockRoleRepo()), "users.invite"))
r.Use(middleware.TrustedProxyRealIP(nil))
r.Use(middleware.NewRateLimiter(maxAttempts, time.Hour))
r.Post("/invite", inviteHandler.CreateInvite)
@@ -108,6 +108,13 @@ func (f *userRolesFixture) seedUser(id uint32, username string, role user.Role,
}
}
func (f *userRolesFixture) seedWriter(id uint32, username string) {
f.seedUser(id, username, "", true)
stored := f.users.users[id]
stored.RoleID = writerRoleID
f.users.users[id] = stored
}
func (f *userRolesFixture) do(t *testing.T, method, target, body string, cookie *http.Cookie) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(method, target, bytes.NewBufferString(body))
@@ -268,25 +275,36 @@ func TestUserRolesHttp(t *testing.T) {
}
})
t.Run("An admin cannot invite", func(t *testing.T) {
t.Run("An admin can invite", func(t *testing.T) {
f := newUserRolesFixture(t, 100)
f.seedUser(2, "jonas", user.RoleAdmin, true)
rec := f.do(t, http.MethodPost, "/v1/user/invite", rolesInviteBody, f.as(t, 2))
if rec.Code != http.StatusCreated {
t.Fatalf("expected 201, got %d: %s", rec.Code, rec.Body.String())
}
})
t.Run("A writer cannot invite", func(t *testing.T) {
f := newUserRolesFixture(t, 100)
f.seedWriter(2, "jonas")
rec := f.do(t, http.MethodPost, "/v1/user/invite", rolesInviteBody, f.as(t, 2))
if rec.Code != http.StatusForbidden {
t.Fatalf("expected 403, got %d", rec.Code)
}
if rec.Body.String() != apperr.OwnerRequiredErrorMessage+"\n" {
if rec.Body.String() != apperr.MissingPermissionErrorMessage+"\n" {
t.Errorf("unexpected body %q", rec.Body.String())
}
assertNothingInvited(t, f)
})
for _, body := range []string{`{"email": "not-an-email"}`, `not json`} {
t.Run("The owner check comes before validation and the rate limiter: "+body, func(t *testing.T) {
t.Run("The permission check comes before validation and the rate limiter: "+body, func(t *testing.T) {
f := newUserRolesFixture(t, 100)
f.seedUser(2, "jonas", user.RoleAdmin, true)
f.seedWriter(2, "jonas")
rec := f.do(t, http.MethodPost, "/v1/user/invite", body, f.as(t, 2))
@@ -297,9 +315,9 @@ func TestUserRolesHttp(t *testing.T) {
})
}
t.Run("An admin's refused invites do not use up the rate limit", func(t *testing.T) {
t.Run("A writer's refused invites do not use up the rate limit", func(t *testing.T) {
f := newUserRolesFixture(t, 2)
f.seedUser(2, "jonas", user.RoleAdmin, true)
f.seedWriter(2, "jonas")
for i := 0; i < 3; i++ {
rec := f.do(t, http.MethodPost, "/v1/user/invite", rolesInviteBody, f.as(t, 2))
@@ -314,7 +332,7 @@ func TestUserRolesHttp(t *testing.T) {
f.seedUser(2, "jonas", user.RoleOwner, true)
cookie := f.as(t, 2)
stored := f.users.users[2]
stored.Role = user.RoleAdmin
stored.RoleID = writerRoleID
f.users.users[2] = stored
rec := f.do(t, http.MethodPost, "/v1/user/invite", rolesInviteBody, cookie)
@@ -384,7 +402,7 @@ func TestUserRolesUseCases(t *testing.T) {
})
}
func TestRequireOwnerRepositoryFailure(t *testing.T) {
func TestRequirePermissionRepositoryFailure(t *testing.T) {
t.Run("An unexpected repository error is a 500, not a 403", func(t *testing.T) {
token := authtoken.New([]byte("test-secret"))
repo := newMockUserRepo()
@@ -392,7 +410,7 @@ func TestRequireOwnerRepositoryFailure(t *testing.T) {
r := chi.NewRouter()
r.Use(authtoken.Verifier(token))
r.Use(authtoken.Authenticator)
r.Use(middleware.RequireOwner(useCases.NewGetUserUseCase(repo)))
r.Use(middleware.RequirePermission(useCases.NewGetUserUseCase(repo), useCases.NewCheckPermissionUseCase(newSeededMockRoleRepo()), "users.invite"))
r.Post("/", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
repo.fail.failNext("Get")