refactor: centralize HTTP error handling (#2062)

This changes the error handling to a centralized HTTP error handler in `pkg/routes/error_handler.go` that converts all error types to proper HTTP responses. This simplifies the overall error handling because http handler now only need to return the error instead of calling HandleHTTPError as previously.
It also removes the duplication between handling errors with and without Sentry.

🐰 Hop along, dear errors, no more wrapping today!
We've centralized handlers in a shiny new way,
From scattered to unified, the code flows so clean,
ValidationHTTPError marshals JSON supreme!
Direct propagation hops forward with glee,
A refactor so grand—what a sight to see! 🎉
This commit is contained in:
kolaente 2026-01-08 11:02:59 +01:00 committed by GitHub
parent 4f31300915
commit 39b4568bc5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 750 additions and 447 deletions

View File

@ -17,6 +17,7 @@
package models package models
import ( import (
"encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings" "strings"
@ -110,6 +111,26 @@ func (err ValidationHTTPError) Error() string {
return theErr.Error() return theErr.Error()
} }
// MarshalJSON implements json.Marshaler to ensure InvalidFields is included in the JSON response.
// This is needed because Echo's DefaultHTTPErrorHandler converts error types to {"message": err.Error()},
// losing structured data. By implementing json.Marshaler, Echo will serialize the full struct.
func (err ValidationHTTPError) MarshalJSON() ([]byte, error) {
return json.Marshal(struct {
Code int `json:"code"`
Message string `json:"message"`
InvalidFields []string `json:"invalid_fields"`
}{
Code: err.Code,
Message: err.Message,
InvalidFields: err.InvalidFields,
})
}
// GetHTTPCode returns the HTTP status code for this error.
func (err ValidationHTTPError) GetHTTPCode() int {
return err.HTTPCode
}
func InvalidFieldError(fields []string) error { func InvalidFieldError(fields []string) error {
return InvalidFieldErrorWithMessage(fields, "Invalid Data") return InvalidFieldErrorWithMessage(fields, "Invalid Data")
} }
@ -153,6 +174,42 @@ func (err ErrInvalidTimezone) HTTPError() web.HTTPError {
} }
} }
// ErrInvalidModel represents an error where the request body could not be parsed
type ErrInvalidModel struct {
Message string
Err error // Original error for unwrapping
}
// IsErrInvalidModel checks if an error is ErrInvalidModel.
func IsErrInvalidModel(err error) bool {
_, ok := err.(ErrInvalidModel)
return ok
}
func (err ErrInvalidModel) Error() string {
if err.Message != "" {
return fmt.Sprintf("Invalid model provided: %s", err.Message)
}
return "Invalid model provided."
}
// Unwrap returns the wrapped error for use with errors.Is/errors.As
func (err ErrInvalidModel) Unwrap() error {
return err.Err
}
// ErrCodeInvalidModel holds the unique world-error code of this error
const ErrCodeInvalidModel = 2004
// HTTPError holds the http error description
func (err ErrInvalidModel) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusBadRequest,
Code: ErrCodeInvalidModel,
Message: err.Error(),
}
}
// =========== // ===========
// Project errors // Project errors
// =========== // ===========

View File

@ -34,7 +34,6 @@ import (
"code.vikunja.io/api/pkg/modules/avatar/upload" "code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/web/handler"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
petname "github.com/dustinkirkland/golang-petname" petname "github.com/dustinkirkland/golang-petname"
@ -140,12 +139,12 @@ func HandleCallback(c echo.Context) error {
"details": detailedErr.Details, "details": detailedErr.Details,
}) })
} }
return handler.HandleHTTPError(err) return err
} }
cl, err := getClaims(provider, oauthToken, idToken) cl, err := getClaims(provider, oauthToken, idToken)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -156,21 +155,21 @@ func HandleCallback(c echo.Context) error {
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
log.Errorf("Error creating new user for provider %s: %v", provider.Name, err) log.Errorf("Error creating new user for provider %s: %v", provider.Name, err)
return handler.HandleHTTPError(err) return err
} }
teamData := getTeamDataFromToken(cl.VikunjaGroups, provider) teamData := getTeamDataFromToken(cl.VikunjaGroups, provider)
err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, "OIDC") err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, "OIDC")
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
log.Errorf("Error creating new team for provider %s: %v", provider.Name, err) log.Errorf("Error creating new team for provider %s: %v", provider.Name, err)
return handler.HandleHTTPError(err) return err
} }
// Create token // Create token

View File

@ -43,7 +43,6 @@ import (
"code.vikunja.io/api/pkg/modules/background/unsplash" "code.vikunja.io/api/pkg/modules/background/unsplash"
"code.vikunja.io/api/pkg/modules/background/upload" "code.vikunja.io/api/pkg/modules/background/upload"
"code.vikunja.io/api/pkg/web" "code.vikunja.io/api/pkg/web"
"code.vikunja.io/api/pkg/web/handler"
"github.com/bbrks/go-blurhash" "github.com/bbrks/go-blurhash"
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
@ -138,7 +137,7 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
project, auth, err := bp.setBackgroundPreparations(s, c) project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
p := bp.Provider() p := bp.Provider()
@ -153,13 +152,13 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
err = p.Set(s, image, project, auth) err = p.Set(s, image, project, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = project.ReadOne(s, auth) err = project.ReadOne(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, project) return c.JSON(http.StatusOK, project)
@ -185,7 +184,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
project, auth, err := bp.setBackgroundPreparations(s, c) project, auth, err := bp.setBackgroundPreparations(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Get + upload the image // Get + upload the image
@ -205,7 +204,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
mime, err := mimetype.DetectReader(srcf) mime, err := mimetype.DetectReader(srcf)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if !strings.HasPrefix(mime.String(), "image") { if !strings.HasPrefix(mime.String(), "image") {
_ = s.Rollback() _ = s.Rollback()
@ -233,18 +232,18 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Unsupported image format. Allowed: " + strings.Join(allowedImageMimes, ",")}) return c.JSON(http.StatusBadRequest, models.Message{Message: "Unsupported image format. Allowed: " + strings.Join(allowedImageMimes, ",")})
} }
return handler.HandleHTTPError(err) return err
} }
err = project.ReadOne(s, auth) err = project.ReadOne(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, project) return c.JSON(http.StatusOK, project)
@ -314,7 +313,7 @@ func checkProjectBackgroundRights(s *xorm.Session, c echo.Context) (project *mod
can, _, err := project.CanRead(s, auth) can, _, err := project.CanRead(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return nil, auth, handler.HandleHTTPError(err) return nil, auth, err
} }
if !can { if !can {
_ = s.Rollback() _ = s.Rollback()
@ -359,12 +358,12 @@ func GetProjectBackground(c echo.Context) error {
} }
if err := bgFile.LoadFileByID(); err != nil { if err := bgFile.LoadFileByID(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
stat, err := bgFile.File.Stat() stat, err := bgFile.File.Stat()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Unsplash requires pingbacks as per their api usage guidelines. // Unsplash requires pingbacks as per their api usage guidelines.
@ -374,7 +373,7 @@ func GetProjectBackground(c echo.Context) error {
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files // Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files

View File

@ -21,7 +21,6 @@ import (
"net/http" "net/http"
"strings" "strings"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -56,7 +55,7 @@ func unsplashImage(url string, c echo.Context) error {
func ProxyUnsplashImage(c echo.Context) error { func ProxyUnsplashImage(c echo.Context) error {
photo, err := getUnsplashPhotoInfoByID(c.Param("image")) photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
pingbackByPhotoID(photo.ID) pingbackByPhotoID(photo.ID)
return unsplashImage(photo.Urls.Raw, c) return unsplashImage(photo.Urls.Raw, c)
@ -76,7 +75,7 @@ func ProxyUnsplashImage(c echo.Context) error {
func ProxyUnsplashThumb(c echo.Context) error { func ProxyUnsplashThumb(c echo.Context) error {
photo, err := getUnsplashPhotoInfoByID(c.Param("image")) photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
pingbackByPhotoID(photo.ID) pingbackByPhotoID(photo.ID)
return unsplashImage("https://images.unsplash.com/"+getImageID(photo.Urls.Raw)+"?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ", c) return unsplashImage("https://images.unsplash.com/"+getImageID(photo.Urls.Raw)+"?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ", c)

View File

@ -21,19 +21,18 @@ import (
"code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user" user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
func status(ms migration.MigratorName, c echo.Context) error { func status(ms migration.MigratorName, c echo.Context) error {
user, err := user2.GetCurrentUser(c) user, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
status, err := migration.GetMigrationStatus(ms, user) status, err := migration.GetMigrationStatus(ms, user)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, status) return c.JSON(http.StatusOK, status)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user" user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -65,12 +64,12 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
// Get the user from context // Get the user from context
user, err := user2.GetCurrentUser(c) user, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
stats, err := migration.GetMigrationStatus(ms, user) stats, err := migration.GetMigrationStatus(ms, user)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
if !stats.StartedAt.IsZero() && stats.FinishedAt.IsZero() { if !stats.StartedAt.IsZero() && stats.FinishedAt.IsZero() {
@ -92,7 +91,7 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
User: user, User: user,
}) })
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "Migration was started successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "Migration was started successfully."})

View File

@ -22,7 +22,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user" user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -44,7 +43,7 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
// Get the user from context // Get the user from context
user, err := user2.GetCurrentUser(c) user, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
file, err := c.FormFile("import") file, err := c.FormFile("import")
@ -59,18 +58,18 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
m, err := migration.StartMigration(ms, user) m, err := migration.StartMigration(ms, user)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
// Do the migration // Do the migration
err = ms.Migrate(user, src, file.Size) err = ms.Migrate(user, src, file.Size)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
err = migration.FinishMigration(m) err = migration.FinishMigration(m)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})

View File

@ -25,7 +25,6 @@ import (
"code.vikunja.io/api/pkg/modules/avatar/empty" "code.vikunja.io/api/pkg/modules/avatar/empty"
"code.vikunja.io/api/pkg/modules/avatar/upload" "code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"io" "io"
"net/http" "net/http"
@ -58,7 +57,7 @@ func GetAvatar(c echo.Context) error {
u, err := user.GetUserWithEmail(s, &user.User{Username: username}) u, err := user.GetUserWithEmail(s, &user.User{Username: username})
if err != nil && !user.IsErrUserDoesNotExist(err) { if err != nil && !user.IsErrUserDoesNotExist(err) {
log.Errorf("Error getting user for avatar: %v", err) log.Errorf("Error getting user for avatar: %v", err)
return handler.HandleHTTPError(err) return err
} }
found := err == nil || !user.IsErrUserDoesNotExist(err) found := err == nil || !user.IsErrUserDoesNotExist(err)
@ -75,7 +74,7 @@ func GetAvatar(c echo.Context) error {
sizeInt, err = strconv.ParseInt(size, 10, 64) sizeInt, err = strconv.ParseInt(size, 10, 64)
if err != nil { if err != nil {
log.Errorf("Error parsing size: %v", err) log.Errorf("Error parsing size: %v", err)
return handler.HandleHTTPError(err) return models.ErrInvalidModel{Message: "Invalid size parameter"}
} }
} }
if sizeInt > config.ServiceMaxAvatarSize.GetInt64() { if sizeInt > config.ServiceMaxAvatarSize.GetInt64() {
@ -86,7 +85,7 @@ func GetAvatar(c echo.Context) error {
a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt) a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt)
if err != nil { if err != nil {
log.Errorf("Error getting avatar for user %d: %v", u.ID, err) log.Errorf("Error getting avatar for user %d: %v", u.ID, err)
return handler.HandleHTTPError(err) return err
} }
return c.Blob(http.StatusOK, mimeType, a) return c.Blob(http.StatusOK, mimeType, a)
@ -112,12 +111,12 @@ func UploadAvatar(c echo.Context) (err error) {
uc, err := user.GetCurrentUser(c) uc, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
u, err := user.GetUserByID(s, uc.ID) u, err := user.GetUserByID(s, uc.ID)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Get + upload the image // Get + upload the image
@ -137,7 +136,7 @@ func UploadAvatar(c echo.Context) (err error) {
mime, err := mimetype.DetectReader(src) mime, err := mimetype.DetectReader(src)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if !strings.HasPrefix(mime.String(), "image") { if !strings.HasPrefix(mime.String(), "image") {
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."}) return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
@ -148,12 +147,12 @@ func UploadAvatar(c echo.Context) (err error) {
err = upload.StoreAvatarFile(s, u, src) err = upload.StoreAvatarFile(s, u, src)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
avatar.FlushAllCaches(u) avatar.FlushAllCaches(u)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -56,7 +55,7 @@ func AuthenticateLinkShare(c echo.Context) error {
sh := &LinkShareAuth{} sh := &LinkShareAuth{}
err := c.Bind(sh) err := c.Bind(sh)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -64,19 +63,19 @@ func AuthenticateLinkShare(c echo.Context) error {
share, err := models.GetLinkShareByHash(s, sh.Hash) share, err := models.GetLinkShareByHash(s, sh.Hash)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
if share.SharingType == models.SharingTypeWithPassword { if share.SharingType == models.SharingTypeWithPassword {
err := models.VerifyLinkSharePassword(share, sh.Password) err := models.VerifyLinkSharePassword(share, sh.Password)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
} }
t, err := auth.NewLinkShareJWTAuthtoken(share) t, err := auth.NewLinkShareJWTAuthtoken(share)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
share.Password = "" share.Password = ""

View File

@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/modules/auth/ldap" "code.vikunja.io/api/pkg/modules/auth/ldap"
"code.vikunja.io/api/pkg/modules/keyvalue" "code.vikunja.io/api/pkg/modules/keyvalue"
user2 "code.vikunja.io/api/pkg/user" user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -58,7 +57,7 @@ func Login(c echo.Context) (err error) {
user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString()) user, err = ldap.AuthenticateUserInLDAP(s, u.Username, u.Password, config.AuthLdapGroupSyncEnabled.GetBool(), config.AuthLdapAvatarSyncAttribute.GetString())
if err != nil && !user2.IsErrWrongUsernameOrPassword(err) { if err != nil && !user2.IsErrWrongUsernameOrPassword(err) {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
} }
@ -67,25 +66,25 @@ func Login(c echo.Context) (err error) {
user, err = user2.CheckUserCredentials(s, &u) user, err = user2.CheckUserCredentials(s, &u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
} }
if user.Status == user2.StatusDisabled { if user.Status == user2.StatusDisabled {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(&user2.ErrAccountDisabled{UserID: user.ID}) return &user2.ErrAccountDisabled{UserID: user.ID}
} }
totpEnabled, err := user2.TOTPEnabledForUser(s, user) totpEnabled, err := user2.TOTPEnabledForUser(s, user)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if totpEnabled { if totpEnabled {
if u.TOTPPasscode == "" { if u.TOTPPasscode == "" {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(user2.ErrInvalidTOTPPasscode{}) return user2.ErrInvalidTOTPPasscode{}
} }
_, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{ _, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{
@ -97,7 +96,7 @@ func Login(c echo.Context) (err error) {
user2.HandleFailedTOTPAuth(s, user) user2.HandleFailedTOTPAuth(s, user)
} }
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
} }
@ -110,7 +109,7 @@ func Login(c echo.Context) (err error) {
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Create token // Create token
@ -141,12 +140,12 @@ func RenewToken(c echo.Context) (err error) {
err := share.ReadOne(s, share) err := share.ReadOne(s, share)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
t, err := auth.NewLinkShareJWTAuthtoken(share) t, err := auth.NewLinkShareJWTAuthtoken(share)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, auth.Token{Token: t}) return c.JSON(http.StatusOK, auth.Token{Token: t})
} }
@ -154,18 +153,18 @@ func RenewToken(c echo.Context) (err error) {
u, err := user2.GetUserFromClaims(claims) u, err := user2.GetUserFromClaims(claims)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID}) user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
var long bool var long bool

View File

@ -27,11 +27,33 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
auth2 "code.vikunja.io/api/pkg/modules/auth" auth2 "code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
// attachmentUploadError represents a structured error for attachment upload failures
type attachmentUploadError struct {
Code int `json:"code,omitempty"`
Message string `json:"message"`
}
// toAttachmentUploadError converts an error to a structured attachmentUploadError
func toAttachmentUploadError(err error) attachmentUploadError {
// Try to get structured error info from HTTPErrorProcessor
if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
errDetails := httpErr.HTTPError()
return attachmentUploadError{
Code: errDetails.Code,
Message: errDetails.Message,
}
}
// Fall back to just the error message
return attachmentUploadError{
Message: err.Error(),
}
}
// UploadTaskAttachment handles everything needed for the upload of a task attachment // UploadTaskAttachment handles everything needed for the upload of a task attachment
// @Summary Upload a task attachment // @Summary Upload a task attachment
// @Description Upload a task attachment. You can pass multiple files with the files form param. // @Description Upload a task attachment. You can pass multiple files with the files form param.
@ -56,7 +78,7 @@ func UploadTaskAttachment(c echo.Context) error {
// Permissions check // Permissions check
auth, err := auth2.GetAuthFromClaims(c) auth, err := auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -65,7 +87,7 @@ func UploadTaskAttachment(c echo.Context) error {
can, err := taskAttachment.CanCreate(s, auth) can, err := taskAttachment.CanCreate(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if !can { if !can {
return echo.ErrForbidden return echo.ErrForbidden
@ -78,11 +100,11 @@ func UploadTaskAttachment(c echo.Context) error {
if errors.Is(err, http.ErrNotMultipart) { if errors.Is(err, http.ErrNotMultipart) {
return echo.NewHTTPError(http.StatusBadRequest, "No multipart form provided") return echo.NewHTTPError(http.StatusBadRequest, "No multipart form provided")
} }
return handler.HandleHTTPError(err) return err
} }
type result struct { type result struct {
Errors []*echo.HTTPError `json:"errors"` Errors []attachmentUploadError `json:"errors"`
Success []*models.TaskAttachment `json:"success"` Success []*models.TaskAttachment `json:"success"`
} }
r := &result{} r := &result{}
@ -95,14 +117,14 @@ func UploadTaskAttachment(c echo.Context) error {
f, err := file.Open() f, err := file.Open()
if err != nil { if err != nil {
r.Errors = append(r.Errors, handler.HandleHTTPError(err)) r.Errors = append(r.Errors, toAttachmentUploadError(err))
continue continue
} }
defer f.Close() defer f.Close()
err = ta.NewAttachment(s, f, file.Filename, uint64(file.Size), auth) err = ta.NewAttachment(s, f, file.Filename, uint64(file.Size), auth)
if err != nil { if err != nil {
r.Errors = append(r.Errors, handler.HandleHTTPError(err)) r.Errors = append(r.Errors, toAttachmentUploadError(err))
continue continue
} }
r.Success = append(r.Success, ta) r.Success = append(r.Success, ta)
@ -110,7 +132,7 @@ func UploadTaskAttachment(c echo.Context) error {
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, r) return c.JSON(http.StatusOK, r)
@ -140,7 +162,7 @@ func GetTaskAttachment(c echo.Context) error {
// Permissions check // Permissions check
auth, err := auth2.GetAuthFromClaims(c) auth, err := auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -149,7 +171,7 @@ func GetTaskAttachment(c echo.Context) error {
can, _, err := taskAttachment.CanRead(s, auth) can, _, err := taskAttachment.CanRead(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if !can { if !can {
return echo.ErrForbidden return echo.ErrForbidden
@ -159,7 +181,7 @@ func GetTaskAttachment(c echo.Context) error {
err = taskAttachment.ReadOne(s, auth) err = taskAttachment.ReadOne(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// If the preview query parameter is set, get the preview (cached or generate) // If the preview query parameter is set, get the preview (cached or generate)
@ -175,12 +197,12 @@ func GetTaskAttachment(c echo.Context) error {
err = taskAttachment.File.LoadFileByID() err = taskAttachment.File.LoadFileByID()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if config.FilesType.GetString() == "s3" { if config.FilesType.GetString() == "s3" {
// s3 files cannot use http.ServeContent as it requires a Seekable file // s3 files cannot use http.ServeContent as it requires a Seekable file
@ -193,7 +215,7 @@ func GetTaskAttachment(c echo.Context) error {
// Stream the file content directly to the response // Stream the file content directly to the response
_, err = io.Copy(c.Response().Writer, taskAttachment.File.File) _, err = io.Copy(c.Response().Writer, taskAttachment.File.File)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
} else { } else {
http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File) http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -43,12 +42,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
u, err := user.GetCurrentUser(c) u, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
token, err := user.GenerateNewCaldavToken(u) token, err := user.GenerateNewCaldavToken(u)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusCreated, token) return c.JSON(http.StatusCreated, token)
@ -69,12 +68,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
func GetCaldavTokens(c echo.Context) error { func GetCaldavTokens(c echo.Context) error {
u, err := user.GetCurrentUser(c) u, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
tokens, err := user.GetCaldavTokens(u) tokens, err := user.GetCaldavTokens(u)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, tokens) return c.JSON(http.StatusOK, tokens)
@ -95,17 +94,17 @@ func GetCaldavTokens(c echo.Context) error {
func DeleteCaldavToken(c echo.Context) error { func DeleteCaldavToken(c echo.Context) error {
u, err := user.GetCurrentUser(c) u, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
id, err := strconv.ParseInt(c.Param("id"), 10, 64) id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
err = user.DeleteCaldavTokenByID(u, id) err = user.DeleteCaldavTokenByID(u, id)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, &models.Message{Message: "The token was deleted successfully."}) return c.JSON(http.StatusOK, &models.Message{Message: "The token was deleted successfully."})

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -51,12 +50,12 @@ func UserConfirmEmail(c echo.Context) error {
err := user.ConfirmEmail(s, &emailConfirm) err := user.ConfirmEmail(s, &emailConfirm)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "The email was confirmed successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "The email was confirmed successfully."})

View File

@ -22,7 +22,6 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -53,13 +52,13 @@ func UserRequestDeletion(c echo.Context) error {
err := s.Begin() err := s.Begin()
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
u, err := user.GetCurrentUserFromDB(s, c) u, err := user.GetCurrentUserFromDB(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if u.IsLocalUser() { if u.IsLocalUser() {
@ -76,20 +75,20 @@ func UserRequestDeletion(c echo.Context) error {
err = user.CheckUserPassword(u, deletionRequest.Password) err = user.CheckUserPassword(u, deletionRequest.Password)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err).SetInternal(err) return err
} }
} }
err = user.RequestDeletion(s, u) err = user.RequestDeletion(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested deletion."}) return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested deletion."})
@ -122,25 +121,25 @@ func UserConfirmDeletion(c echo.Context) error {
err = s.Begin() err = s.Begin()
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
u, err := user.GetCurrentUserFromDB(s, c) u, err := user.GetCurrentUserFromDB(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = user.ConfirmDeletion(s, u, deleteConfirmation.Token) err = user.ConfirmDeletion(s, u, deleteConfirmation.Token)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."}) return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."})
@ -164,13 +163,13 @@ func UserCancelDeletion(c echo.Context) error {
err := s.Begin() err := s.Begin()
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
u, err := user.GetCurrentUserFromDB(s, c) u, err := user.GetCurrentUserFromDB(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if u.IsLocalUser() { if u.IsLocalUser() {
@ -187,20 +186,20 @@ func UserCancelDeletion(c echo.Context) error {
err = user.CheckUserPassword(u, deletionRequest.Password) err = user.CheckUserPassword(u, deletionRequest.Password)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
} }
err = user.CancelDeletion(s, u) err = user.CancelDeletion(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."}) return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."})

View File

@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/files" "code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"xorm.io/xorm" "xorm.io/xorm"
) )
@ -37,13 +36,13 @@ func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err erro
err = s.Begin() err = s.Begin()
if err != nil { if err != nil {
return nil, nil, handler.HandleHTTPError(err) return nil, nil, err
} }
u, err = user.GetCurrentUserFromDB(s, c) u, err = user.GetCurrentUserFromDB(s, c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return nil, nil, handler.HandleHTTPError(err) return nil, nil, err
} }
// Users authenticated with a third-party are unable to provide their password. // Users authenticated with a third-party are unable to provide their password.
@ -64,7 +63,7 @@ func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err erro
err = user.CheckUserPassword(u, pass.Password) err = user.CheckUserPassword(u, pass.Password)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return nil, nil, handler.HandleHTTPError(err) return nil, nil, err
} }
return return
@ -92,13 +91,13 @@ func RequestUserDataExport(c echo.Context) error {
}) })
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."}) return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
@ -125,7 +124,7 @@ func DownloadUserDataExport(c echo.Context) error {
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Check if user has an export file // Check if user has an export file
@ -141,14 +140,14 @@ func DownloadUserDataExport(c echo.Context) error {
if files.IsErrFileDoesNotExist(err) { if files.IsErrFileDoesNotExist(err) {
return exportNotFoundError return exportNotFoundError
} }
return handler.HandleHTTPError(err) return err
} }
err = exportFile.LoadFileByID() err = exportFile.LoadFileByID()
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return exportNotFoundError return exportNotFoundError
} }
return handler.HandleHTTPError(err) return err
} }
http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File) http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File)
@ -175,7 +174,7 @@ func GetUserExportStatus(c echo.Context) error {
u, err := user.GetCurrentUserFromDB(s, c) u, err := user.GetCurrentUserFromDB(s, c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
if u.ExportFileID == 0 { if u.ExportFileID == 0 {
@ -184,7 +183,7 @@ func GetUserExportStatus(c echo.Context) error {
exportFile := &files.File{ID: u.ExportFileID} exportFile := &files.File{ID: u.ExportFileID}
if err := exportFile.LoadFileMetaByID(); err != nil { if err := exportFile.LoadFileMetaByID(); err != nil {
return handler.HandleHTTPError(err) return err
} }
status := UserExportStatus{ status := UserExportStatus{

View File

@ -25,7 +25,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
auth2 "code.vikunja.io/api/pkg/modules/auth" auth2 "code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -50,13 +49,13 @@ func UserList(c echo.Context) error {
currentUser, err := user.GetCurrentUser(c) currentUser, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
users, err := user.ListUsers(s, search, currentUser, nil) users, err := user.ListUsers(s, search, currentUser, nil)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Obfuscate the mailadresses // Obfuscate the mailadresses
@ -93,7 +92,7 @@ func ListUsersForProject(c echo.Context) error {
project := models.Project{ID: projectID} project := models.Project{ID: projectID}
auth, err := auth2.GetAuthFromClaims(c) auth, err := auth2.GetAuthFromClaims(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -102,7 +101,7 @@ func ListUsersForProject(c echo.Context) error {
canRead, _, err := project.CanRead(s, auth) canRead, _, err := project.CanRead(s, auth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if !canRead { if !canRead {
return echo.ErrForbidden return echo.ErrForbidden
@ -111,19 +110,19 @@ func ListUsersForProject(c echo.Context) error {
currentUser, err := user.GetCurrentUser(c) currentUser, err := user.GetCurrentUser(c)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
search := c.QueryParam("s") search := c.QueryParam("s")
users, err := models.ListUsersFromProject(s, &project, currentUser, search) users, err := models.ListUsersFromProject(s, &project, currentUser, search)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, users) return c.JSON(http.StatusOK, users)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -51,12 +50,12 @@ func UserResetPassword(c echo.Context) error {
err := user.ResetPassword(s, &pwReset) err := user.ResetPassword(s, &pwReset)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."})
@ -90,12 +89,12 @@ func UserRequestResetPasswordToken(c echo.Context) error {
err := user.RequestUserPasswordResetTokenByEmail(s, &pwTokenReset) err := user.RequestUserPasswordResetTokenByEmail(s, &pwTokenReset)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "Token was sent."}) return c.JSON(http.StatusOK, models.Message{Message: "Token was sent."})

View File

@ -24,7 +24,6 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -61,7 +60,7 @@ func RegisterUser(c echo.Context) error {
return c.JSON(e.HTTPCode, e) return c.JSON(e.HTTPCode, e)
} }
return handler.HandleHTTPError(err) return err
} }
if userIn == nil { if userIn == nil {
return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."}) return c.JSON(http.StatusBadRequest, models.Message{Message: "No or invalid user model provided."})
@ -79,19 +78,19 @@ func RegisterUser(c echo.Context) error {
}) })
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Create their initial project // Create their initial project
err = models.CreateNewProjectForUser(s, newUser) err = models.CreateNewProjectForUser(s, newUser)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, newUser) return c.JSON(http.StatusOK, newUser)

View File

@ -28,7 +28,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/avatar" "code.vikunja.io/api/pkg/modules/avatar"
user2 "code.vikunja.io/api/pkg/user" user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
) )
// UserAvatarProvider holds the user avatar provider type // UserAvatarProvider holds the user avatar provider type
@ -81,7 +80,7 @@ func GetUserAvatarProvider(c echo.Context) error {
u, err := user2.GetCurrentUser(c) u, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -90,12 +89,12 @@ func GetUserAvatarProvider(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID}) user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
uap := &UserAvatarProvider{AvatarProvider: user.AvatarProvider} uap := &UserAvatarProvider{AvatarProvider: user.AvatarProvider}
@ -124,7 +123,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
u, err := user2.GetCurrentUser(c) u, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -133,7 +132,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID}) user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
oldProvider := user.AvatarProvider oldProvider := user.AvatarProvider
@ -143,7 +142,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
_, err = user2.UpdateUser(s, user, false) _, err = user2.UpdateUser(s, user, false)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if user.AvatarProvider == "initials" { if user.AvatarProvider == "initials" {
@ -152,7 +151,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if oldProvider != user.AvatarProvider { if oldProvider != user.AvatarProvider {
@ -179,9 +178,9 @@ func UpdateGeneralUserSettings(c echo.Context) error {
if err != nil { if err != nil {
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
err = c.Validate(us) err = c.Validate(us)
@ -191,7 +190,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
u, err := user2.GetCurrentUser(c) u, err := user2.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -200,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID}) user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
invalidateAvatar := user.AvatarProvider == "initials" && user.Name != us.Name invalidateAvatar := user.AvatarProvider == "initials" && user.Name != us.Name
@ -220,12 +219,12 @@ func UpdateGeneralUserSettings(c echo.Context) error {
_, err = user2.UpdateUser(s, user, true) _, err = user2.UpdateUser(s, user, true)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if invalidateAvatar { if invalidateAvatar {

View File

@ -29,7 +29,6 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -63,7 +62,7 @@ func UserShow(c echo.Context) error {
u, err := models.GetUserOrLinkShareUser(s, a) u, err := models.GetUserOrLinkShareUser(s, a)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
us := &UserWithSettings{ us := &UserWithSettings{
@ -88,7 +87,7 @@ func UserShow(c echo.Context) error {
us.AuthProvider, err = getAuthProviderName(u) us.AuthProvider, err = getAuthProviderName(u)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, us) return c.JSON(http.StatusOK, us)

View File

@ -27,7 +27,6 @@ import (
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"xorm.io/xorm" "xorm.io/xorm"
@ -66,19 +65,19 @@ func getLocalUserFromContext(c echo.Context) (*user.User, *xorm.Session, error)
func UserTOTPEnroll(c echo.Context) error { func UserTOTPEnroll(c echo.Context) error {
u, s, err := getLocalUserFromContext(c) u, s, err := getLocalUserFromContext(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
defer s.Close() defer s.Close()
t, err := user.EnrollTOTP(s, u) t, err := user.EnrollTOTP(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, t) return c.JSON(http.StatusOK, t)
@ -101,7 +100,7 @@ func UserTOTPEnroll(c echo.Context) error {
func UserTOTPEnable(c echo.Context) error { func UserTOTPEnable(c echo.Context) error {
u, s, err := getLocalUserFromContext(c) u, s, err := getLocalUserFromContext(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
defer s.Close() defer s.Close()
@ -112,20 +111,20 @@ func UserTOTPEnable(c echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
err = user.EnableTOTP(s, passcode) err = user.EnableTOTP(s, passcode)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."})
@ -150,35 +149,35 @@ func UserTOTPDisable(c echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
u, s, err := getLocalUserFromContext(c) u, s, err := getLocalUserFromContext(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
defer s.Close() defer s.Close()
err = user.CheckUserPassword(u, login.Password) err = user.CheckUserPassword(u, login.Password)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = user.DisableTOTP(s, u) err = user.DisableTOTP(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "TOTP was enabled successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "TOTP was disabled successfully."})
} }
// UserTOTPQrCode is the handler to show a qr code to enroll the user into totp // UserTOTPQrCode is the handler to show a qr code to enroll the user into totp
@ -194,26 +193,26 @@ func UserTOTPDisable(c echo.Context) error {
func UserTOTPQrCode(c echo.Context) error { func UserTOTPQrCode(c echo.Context) error {
u, s, err := getLocalUserFromContext(c) u, s, err := getLocalUserFromContext(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
defer s.Close() defer s.Close()
qrcode, err := user.GetTOTPQrCodeForUser(s, u) qrcode, err := user.GetTOTPQrCodeForUser(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
buff := &bytes.Buffer{} buff := &bytes.Buffer{}
err = jpeg.Encode(buff, qrcode, nil) err = jpeg.Encode(buff, qrcode, nil)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes()) return c.Blob(http.StatusOK, "image/jpeg", buff.Bytes())
@ -232,19 +231,19 @@ func UserTOTPQrCode(c echo.Context) error {
func UserTOTP(c echo.Context) error { func UserTOTP(c echo.Context) error {
u, s, err := getLocalUserFromContext(c) u, s, err := getLocalUserFromContext(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
defer s.Close() defer s.Close()
t, err := user.GetTOTPForUser(s, u) t, err := user.GetTOTPForUser(s, u)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, t) return c.JSON(http.StatusOK, t)

View File

@ -26,7 +26,6 @@ import (
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -50,14 +49,14 @@ func UpdateUserEmail(c echo.Context) (err error) {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
emailUpdate.User, err = user.GetCurrentUser(c) emailUpdate.User, err = user.GetCurrentUser(c)
if err != nil { if err != nil {
return handler.HandleHTTPError(err) return err
} }
s := db.NewSession() s := db.NewSession()
@ -69,18 +68,18 @@ func UpdateUserEmail(c echo.Context) (err error) {
}) })
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
err = user.UpdateEmail(s, emailUpdate) err = user.UpdateEmail(s, emailUpdate)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "We sent you email with a link to confirm your email address."}) return c.JSON(http.StatusOK, models.Message{Message: "We sent you email with a link to confirm your email address."})

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -60,7 +59,7 @@ func UserChangePassword(c echo.Context) error {
} }
if newPW.OldPassword == "" { if newPW.OldPassword == "" {
return handler.HandleHTTPError(user.ErrEmptyOldPassword{}) return user.ErrEmptyOldPassword{}
} }
s := db.NewSession() s := db.NewSession()
@ -69,18 +68,18 @@ func UserChangePassword(c echo.Context) error {
// Check the current password // Check the current password
if _, err = user.CheckUserCredentials(s, &user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil { if _, err = user.CheckUserCredentials(s, &user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
// Update the password // Update the password
if err = user.UpdateUserPassword(s, doer, newPW.NewPassword); err != nil { if err = user.UpdateUserPassword(s, doer, newPW.NewPassword); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
_ = s.Rollback() _ = s.Rollback()
return handler.HandleHTTPError(err) return err
} }
return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."}) return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."})

135
pkg/routes/error_handler.go Normal file
View File

@ -0,0 +1,135 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package routes
import (
"encoding/json"
"errors"
"net/http"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/web"
"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/labstack/echo/v4"
)
// httpCodeGetter is an interface for errors that can provide their HTTP status code.
type httpCodeGetter interface {
GetHTTPCode() int
}
// errorMessage is used to wrap string error messages in a consistent JSON structure.
type errorMessage struct {
Message interface{} `json:"message"`
}
// CreateHTTPErrorHandler creates a centralized HTTP error handler that:
// 1. Converts all error types to proper HTTP responses
// 2. Preserves full error details (like ValidationHTTPError.InvalidFields)
// 3. Handles Sentry reporting for 5xx errors
// 4. Logs all errors appropriately
func CreateHTTPErrorHandler(e *echo.Echo, enableSentry bool) echo.HTTPErrorHandler {
return func(err error, c echo.Context) {
if c.Response().Committed {
return
}
var (
code = http.StatusInternalServerError
message interface{} = http.StatusText(http.StatusInternalServerError)
)
// Keep track of the original error for logging/sentry
originalErr := err
// 1. Check if it's already an echo.HTTPError (from middleware, auth, etc.)
var he *echo.HTTPError
if errors.As(err, &he) {
code = he.Code
message = he.Message
// Check if internal error has more details we should use
if he.Internal != nil {
originalErr = he.Internal
err = he.Internal
}
}
// 2. Special case: 413 body limit → convert to ErrFileIsTooLarge
// This must be checked before other error type checks
if code == http.StatusRequestEntityTooLarge {
fileErr := files.ErrFileIsTooLarge{}
errDetails := fileErr.HTTPError()
code = errDetails.HTTPCode
message = errDetails
} else if _, isMarshaler := err.(json.Marshaler); isMarshaler {
// 3. Check for json.Marshaler (preserves full struct like ValidationHTTPError)
// This allows errors with extra fields (like InvalidFields) to be serialized correctly
if codeGetter, hasCode := err.(httpCodeGetter); hasCode {
code = codeGetter.GetHTTPCode()
}
message = err // Echo will serialize via MarshalJSON
} else if hp, ok := err.(web.HTTPErrorProcessor); ok {
// 4. Standard HTTPErrorProcessor (domain errors like ErrProjectDoesNotExist)
errDetails := hp.HTTPError()
code = errDetails.HTTPCode
message = errDetails
}
// 5. For any other error type, we keep the defaults (500 with generic message)
// or the echo.HTTPError values if it was that type
// Log the error
log.Error(originalErr.Error())
// Sentry reporting for 5xx errors
if enableSentry && code >= 500 {
reportToSentry(originalErr, c)
}
// Send response
if c.Request().Method == http.MethodHead {
err = c.NoContent(code)
} else {
// Wrap string messages in a struct to ensure consistent JSON format
// e.g., "Forbidden" becomes {"message": "Forbidden"}
if _, isString := message.(string); isString {
message = errorMessage{Message: message}
}
err = c.JSON(code, message)
}
if err != nil {
e.Logger.Error(err)
}
}
}
// reportToSentry sends an error to Sentry with request context
func reportToSentry(err error, c echo.Context) {
hub := sentryecho.GetHubFromContext(c)
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("url", c.Request().URL)
hub.CaptureException(err)
})
} else {
sentry.CaptureException(err)
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
}
log.Debugf("Error '%s' sent to sentry", err.Error())
}

View File

@ -52,16 +52,13 @@
package routes package routes
import ( import (
"errors"
"fmt" "fmt"
"log/slog" "log/slog"
"net/http"
"net/url" "net/url"
"strings" "strings"
"time" "time"
"code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth/openid" "code.vikunja.io/api/pkg/modules/auth/openid"
@ -142,17 +139,8 @@ func NewEcho() *echo.Echo {
// Add some overhead for multipart form data (headers, boundaries, etc.) // Add some overhead for multipart form data (headers, boundaries, etc.)
e.Use(middleware.BodyLimit(fmt.Sprintf("%dM", config.GetMaxFileSizeInMBytes()+2))) e.Use(middleware.BodyLimit(fmt.Sprintf("%dM", config.GetMaxFileSizeInMBytes()+2)))
// Set up custom error handler for body limit exceeded when Sentry is not enabled // Set up centralized error handler
if !config.SentryEnabled.GetBool() { e.HTTPErrorHandler = CreateHTTPErrorHandler(e, config.SentryEnabled.GetBool())
e.HTTPErrorHandler = func(err error, c echo.Context) {
// Convert HTTP 413 errors to custom ErrFileIsTooLarge error
var herr *echo.HTTPError
if errors.As(err, &herr) && herr.Code == http.StatusRequestEntityTooLarge {
err = handler.HandleHTTPError(files.ErrFileIsTooLarge{})
}
e.DefaultHTTPErrorHandler(err, c)
}
}
return e return e
} }
@ -174,36 +162,6 @@ func setupSentry(e *echo.Echo) {
e.Use(sentryecho.New(sentryecho.Options{ e.Use(sentryecho.New(sentryecho.Options{
Repanic: true, Repanic: true,
})) }))
e.HTTPErrorHandler = func(err error, c echo.Context) {
// Convert HTTP 413 errors to custom ErrFileIsTooLarge error
var herr *echo.HTTPError
if errors.As(err, &herr) && herr.Code == http.StatusRequestEntityTooLarge {
err = handler.HandleHTTPError(files.ErrFileIsTooLarge{})
}
// Only capture errors not already handled by echo
if errors.As(err, &herr) && herr.Code > 499 {
var errToReport = err
if herr.Internal == nil {
errToReport = herr.Internal
}
hub := sentryecho.GetHubFromContext(c)
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("url", c.Request().URL)
hub.CaptureException(errToReport)
})
} else {
sentry.CaptureException(errToReport)
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
}
log.Debugf("Error '%s' sent to sentry", err.Error())
}
e.DefaultHTTPErrorHandler(err, c)
}
} }
// RegisterRoutes registers all routes for the application // RegisterRoutes registers all routes for the application

View File

@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -38,14 +39,14 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
// Validate the struct // Validate the struct
if err := ctx.Validate(currentStruct); err != nil { if err := ctx.Validate(currentStruct); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err).SetInternal(err) return err
} }
// Get the user to pass for later checks // Get the user to pass for later checks
@ -67,7 +68,7 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
canCreate, err := currentStruct.CanCreate(s, currentAuth) canCreate, err := currentStruct.CanCreate(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
if !canCreate { if !canCreate {
_ = s.Rollback() _ = s.Rollback()
@ -79,17 +80,13 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
err = currentStruct.Create(s, currentAuth) err = currentStruct.Create(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
return HandleHTTPError(err) return err
} }
err = ctx.JSON(http.StatusCreated, currentStruct) return ctx.JSON(http.StatusCreated, currentStruct)
if err != nil {
return HandleHTTPError(err)
}
return err
} }

View File

@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -43,9 +44,9 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
// Check if the user has the permission to delete // Check if the user has the permission to delete
@ -66,7 +67,7 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
canDelete, err := currentStruct.CanDelete(s, currentAuth) canDelete, err := currentStruct.CanDelete(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
if !canDelete { if !canDelete {
_ = s.Rollback() _ = s.Rollback()
@ -77,17 +78,13 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
err = currentStruct.Delete(s, currentAuth) err = currentStruct.Delete(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
return HandleHTTPError(err) return err
} }
err = ctx.JSON(http.StatusOK, message{"Successfully deleted."}) return ctx.JSON(http.StatusOK, message{"Successfully deleted."})
if err != nil {
return HandleHTTPError(err)
}
return err
} }

View File

@ -17,12 +17,7 @@
package handler package handler
import ( import (
"net/http"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/web" "code.vikunja.io/api/pkg/web"
"github.com/labstack/echo/v4"
) )
// WebHandler defines the webhandler object // WebHandler defines the webhandler object
@ -36,13 +31,3 @@ type CObject interface {
web.CRUDable web.CRUDable
web.Permissions web.Permissions
} }
// HandleHTTPError does what it says
func HandleHTTPError(err error) *echo.HTTPError {
log.Error(err.Error())
if a, has := err.(web.HTTPErrorProcessor); has {
errDetails := a.HTTPError()
return echo.NewHTTPError(errDetails.HTTPCode, errDetails).SetInternal(err)
}
return echo.NewHTTPError(http.StatusInternalServerError).SetInternal(err)
}

View File

@ -27,6 +27,7 @@ import (
vconfig "code.vikunja.io/api/pkg/config" vconfig "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -47,9 +48,9 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
// Pagination // Pagination
@ -104,7 +105,7 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber) result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
// Calculate the number of pages from the number of items // Calculate the number of pages from the number of items
@ -126,7 +127,7 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
return HandleHTTPError(err) return err
} }
// Ensure we return an empty array instead of null when there are no results. // Ensure we return an empty array instead of null when there are no results.
@ -136,9 +137,5 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
result = []interface{}{} result = []interface{}{}
} }
err = ctx.JSON(http.StatusOK, result) return ctx.JSON(http.StatusOK, result)
if err != nil {
return HandleHTTPError(err)
}
return err
} }

View File

@ -24,6 +24,7 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -39,9 +40,9 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
// Check permissions // Check permissions
@ -62,7 +63,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
canRead, maxPermission, err := currentStruct.CanRead(s, currentAuth) canRead, maxPermission, err := currentStruct.CanRead(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
if !canRead { if !canRead {
_ = s.Rollback() _ = s.Rollback()
@ -74,7 +75,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
err = currentStruct.ReadOne(s, currentAuth) err = currentStruct.ReadOne(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
// Set the headers // Set the headers
@ -85,12 +86,8 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
return HandleHTTPError(err) return err
} }
err = ctx.JSON(http.StatusOK, currentStruct) return ctx.JSON(http.StatusOK, currentStruct)
if err != nil {
return HandleHTTPError(err)
}
return err
} }

View File

@ -23,6 +23,7 @@ import (
"code.vikunja.io/api/pkg/db" "code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth" "code.vikunja.io/api/pkg/modules/auth"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -39,14 +40,14 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
log.Debugf("Invalid model error. Internal error was: %s", err.Error()) log.Debugf("Invalid model error. Internal error was: %s", err.Error())
var he *echo.HTTPError var he *echo.HTTPError
if errors.As(err, &he) { if errors.As(err, &he) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid model provided. Error was: %s", he.Message)).SetInternal(err) return models.ErrInvalidModel{Message: fmt.Sprintf("%v", he.Message), Err: err}
} }
return echo.NewHTTPError(http.StatusBadRequest, "Invalid model provided.").SetInternal(err) return models.ErrInvalidModel{Err: err}
} }
// Validate the struct // Validate the struct
if err := ctx.Validate(currentStruct); err != nil { if err := ctx.Validate(currentStruct); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err).SetInternal(err) return err
} }
// Check if the user has the permission to do that // Check if the user has the permission to do that
@ -67,7 +68,7 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
canUpdate, err := currentStruct.CanUpdate(s, currentAuth) canUpdate, err := currentStruct.CanUpdate(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
if !canUpdate { if !canUpdate {
_ = s.Rollback() _ = s.Rollback()
@ -79,17 +80,13 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
err = currentStruct.Update(s, currentAuth) err = currentStruct.Update(s, currentAuth)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
return HandleHTTPError(err) return err
} }
err = s.Commit() err = s.Commit()
if err != nil { if err != nil {
return HandleHTTPError(err) return err
} }
err = ctx.JSON(http.StatusOK, currentStruct) return ctx.JSON(http.StatusOK, currentStruct)
if err != nil {
return HandleHTTPError(err)
}
return err
} }

View File

@ -0,0 +1,150 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package webtests
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"code.vikunja.io/api/pkg/modules/auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ErrorResponse represents the expected JSON error structure for standard errors
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
}
// ValidationErrorResponse represents the expected JSON error structure for validation errors
type ValidationErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
InvalidFields []string `json:"invalid_fields"`
}
// TestErrorResponseFormats tests that error responses are correctly serialized to JSON
// This is critical because the error response format is part of the API contract
func TestErrorResponseFormats(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
// Get auth token for testuser1
token, err := auth.NewUserJWTAuthtoken(&testuser1, false)
require.NoError(t, err)
t.Run("validation error returns invalid_fields in JSON body", func(t *testing.T) {
// Update a project with empty title - this should trigger validation error
req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/1", strings.NewReader(`{"title":""}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
// Should be 412 Precondition Failed for validation errors
assert.Equal(t, http.StatusPreconditionFailed, rec.Code)
var errResp ValidationErrorResponse
err := json.Unmarshal(rec.Body.Bytes(), &errResp)
require.NoError(t, err, "Response body: %s", rec.Body.String())
// Verify the error structure includes invalid_fields
assert.Equal(t, 2002, errResp.Code, "Expected error code 2002 (ErrCodeInvalidData)")
require.NotEmpty(t, errResp.InvalidFields, "invalid_fields should not be empty")
require.GreaterOrEqual(t, len(errResp.InvalidFields), 1, "invalid_fields should have at least one element")
assert.Contains(t, errResp.InvalidFields[0], "title", "invalid_fields should mention 'title'")
})
t.Run("bind error returns 400 with message", func(t *testing.T) {
// Send malformed JSON
req := httptest.NewRequest(http.MethodPost, "/api/v1/projects/1", strings.NewReader(`{invalid json`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusBadRequest, rec.Code)
})
t.Run("not found error returns 404 with correct structure", func(t *testing.T) {
// Try to get a project that doesn't exist
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/99999", nil)
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusNotFound, rec.Code)
var errResp ErrorResponse
err := json.Unmarshal(rec.Body.Bytes(), &errResp)
require.NoError(t, err, "Response body: %s", rec.Body.String())
// Should have a proper error code
assert.NotZero(t, errResp.Code, "Error code should be non-zero")
assert.NotEmpty(t, errResp.Message, "Error message should not be empty")
})
t.Run("forbidden error returns 403", func(t *testing.T) {
// Try to access a project owned by user13 (project 20)
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/20", nil)
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusForbidden, rec.Code)
})
t.Run("domain error returns correct code and message", func(t *testing.T) {
// Try to create a project with a nonexistent parent
req := httptest.NewRequest(http.MethodPut, "/api/v1/projects", strings.NewReader(`{"title":"Test","parent_project_id":99999}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
// Should be 404 for nonexistent parent project
assert.Equal(t, http.StatusNotFound, rec.Code)
var errResp ErrorResponse
err := json.Unmarshal(rec.Body.Bytes(), &errResp)
require.NoError(t, err, "Response body: %s", rec.Body.String())
// Verify the error has proper structure
assert.NotZero(t, errResp.Code, "Error code should be non-zero")
assert.NotEmpty(t, errResp.Message, "Error message should not be empty")
})
t.Run("unauthorized request returns 401", func(t *testing.T) {
// Make request without auth token
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects/1", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusUnauthorized, rec.Code)
})
}

View File

@ -181,19 +181,60 @@ func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
t.Error("Error is nil") t.Error("Error is nil")
t.FailNow() t.FailNow()
} }
// First, try to get error code from HTTPErrorProcessor (domain errors like ValidationHTTPError)
if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
assert.Equal(t, expectedErrorCode, httpErr.HTTPError().Code)
return
}
// Fall back to echo.HTTPError for middleware/auth errors
var httperr *echo.HTTPError var httperr *echo.HTTPError
if !errors.As(err, &httperr) { if !errors.As(err, &httperr) {
t.Error("Error is not *echo.HTTPError") t.Errorf("Error is not *echo.HTTPError or web.HTTPErrorProcessor: %T", err)
t.FailNow() t.FailNow()
} }
webhttperr, ok := httperr.Message.(web.HTTPError) webhttperr, ok := httperr.Message.(web.HTTPError)
if !ok { if !ok {
t.Error("Error is not *web.HTTPError") t.Errorf("Error message is not web.HTTPError: %T", httperr.Message)
t.FailNow() t.FailNow()
} }
assert.Equal(t, expectedErrorCode, webhttperr.Code) assert.Equal(t, expectedErrorCode, webhttperr.Code)
} }
// getHTTPErrorCode extracts the HTTP status code from various error types
func getHTTPErrorCode(err error) int {
// First, try domain errors that implement HTTPErrorProcessor
if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
return httpErr.HTTPError().HTTPCode
}
// Fall back to echo.HTTPError
var httperr *echo.HTTPError
if errors.As(err, &httperr) {
return httperr.Code
}
return 0
}
// getHTTPErrorMessage extracts the message from various error types
func getHTTPErrorMessage(err error) interface{} {
// First, try domain errors that implement HTTPErrorProcessor
if httpErr, ok := err.(web.HTTPErrorProcessor); ok {
return httpErr.HTTPError().Message
}
// Then try echo.HTTPError (for Forbidden etc.)
var httperr *echo.HTTPError
if errors.As(err, &httperr) {
return httperr.Message
}
// Fall back to error string
return err.Error()
}
type webHandlerTest struct { type webHandlerTest struct {
user *user.User user *user.User
linkShare *models.LinkSharing linkShare *models.LinkSharing

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -90,7 +89,7 @@ func TestBucket(t *testing.T) {
"view": "4", "view": "4",
}, `{"title":""}`) }, `{"title":""}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required") assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
}) })
t.Run("Permissions check", func(t *testing.T) { t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
@ -101,7 +100,7 @@ func TestBucket(t *testing.T) {
"view": "80", "view": "80",
}, `{"title":"TestLoremIpsum"}`) }, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{ _, err := testHandler.testUpdateWithUser(nil, map[string]string{
@ -110,7 +109,7 @@ func TestBucket(t *testing.T) {
"view": "24", "view": "24",
}, `{"title":"TestLoremIpsum"}`) }, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{ rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@ -138,7 +137,7 @@ func TestBucket(t *testing.T) {
"view": "36", "view": "36",
}, `{"title":"TestLoremIpsum"}`) }, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{ rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@ -166,7 +165,7 @@ func TestBucket(t *testing.T) {
"view": "48", "view": "48",
}, `{"title":"TestLoremIpsum"}`) }, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{ rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@ -194,7 +193,7 @@ func TestBucket(t *testing.T) {
"view": "60", "view": "60",
}, `{"title":"TestLoremIpsum"}`) }, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{ rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
@ -236,12 +235,12 @@ func TestBucket(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{ rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@ -269,7 +268,7 @@ func TestBucket(t *testing.T) {
"view": "36", "view": "36",
}) })
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{ rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@ -297,7 +296,7 @@ func TestBucket(t *testing.T) {
"view": "48", "view": "48",
}) })
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{ rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@ -325,7 +324,7 @@ func TestBucket(t *testing.T) {
"view": "60", "view": "60",
}) })
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{ rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
@ -380,7 +379,7 @@ func TestBucket(t *testing.T) {
"view": "80", "view": "80",
}, `{"title":"Lorem Ipsum"}`) }, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{ _, err := testHandler.testCreateWithUser(nil, map[string]string{
@ -388,7 +387,7 @@ func TestBucket(t *testing.T) {
"view": "24", "view": "24",
}, `{"title":"Lorem Ipsum"}`) }, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{ rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@ -413,7 +412,7 @@ func TestBucket(t *testing.T) {
"view": "36", "view": "36",
}, `{"title":"Lorem Ipsum"}`) }, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{ rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@ -438,7 +437,7 @@ func TestBucket(t *testing.T) {
"view": "48", "view": "48",
}, `{"title":"Lorem Ipsum"}`) }, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{ rec, err := testHandler.testCreateWithUser(nil, map[string]string{
@ -463,7 +462,7 @@ func TestBucket(t *testing.T) {
"view": "60", "view": "60",
}, `{"title":"Lorem Ipsum"}`) }, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{ rec, err := testHandler.testCreateWithUser(nil, map[string]string{

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -69,34 +68,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":0}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":0}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":1}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":2}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Read only access", func(t *testing.T) { t.Run("Read only access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) { t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":0}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":0}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("write", func(t *testing.T) { t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":1}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":2}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Write access", func(t *testing.T) { t.Run("Write access", func(t *testing.T) {
@ -113,7 +112,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("admin", func(t *testing.T) { t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"permission":2}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"permission":2}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Admin access", func(t *testing.T) { t.Run("Admin access", func(t *testing.T) {
@ -197,7 +196,7 @@ func TestLinkSharing(t *testing.T) {
// Project 2, not shared with this token // Project 2, not shared with this token
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"}) _, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the permission to see this`) assert.Contains(t, getHTTPErrorMessage(err), `You don't have the permission to see this`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"}) rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
@ -226,12 +225,12 @@ func TestLinkSharing(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`) _, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`) _, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`) rec, err := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
@ -255,17 +254,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"}) _, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"}) _, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"}) _, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"}) rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
@ -280,23 +279,23 @@ func TestLinkSharing(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) { t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`) _, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Permissions check", func(t *testing.T) { t.Run("Permissions check", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`) _, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -345,34 +344,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`) _, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
@ -380,17 +379,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"}) _, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"}) _, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"}) _, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -437,34 +436,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`) _, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`) _, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`) _, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`) _, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`) _, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`) _, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
@ -472,17 +471,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"}) _, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"}) _, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"}) _, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -590,7 +589,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`) _, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
@ -607,7 +606,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`) _, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`)
@ -625,7 +624,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"}) _, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"}) rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"})
@ -683,17 +682,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`) _, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`) _, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`) _, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
@ -701,17 +700,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"}) _, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"}) _, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"}) _, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -759,34 +758,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`) _, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`) _, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`) _, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
t.Run("Update", func(t *testing.T) { t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`) _, err := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`) _, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`) _, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
@ -794,17 +793,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) { t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"}) _, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared write", func(t *testing.T) { t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"}) _, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared admin", func(t *testing.T) { t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"}) _, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -89,7 +88,7 @@ func TestProject(t *testing.T) {
// Owned by user13 // Owned by user13
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"}) rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the permission to see this`) assert.Contains(t, getHTTPErrorMessage(err), `You don't have the permission to see this`)
assert.Empty(t, rec.Result().Header.Get("x-max-permissions")) assert.Empty(t, rec.Result().Header.Get("x-max-permissions"))
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
@ -192,24 +191,24 @@ func TestProject(t *testing.T) {
t.Run("Empty title", func(t *testing.T) { t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required") assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
}) })
t.Run("Title too long", func(t *testing.T) { t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)") assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
}) })
t.Run("Permissions check", func(t *testing.T) { t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`)
@ -225,7 +224,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`)
@ -241,7 +240,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
@ -257,7 +256,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
@ -287,17 +286,17 @@ func TestProject(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"})
@ -308,12 +307,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"})
@ -324,12 +323,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) { t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
@ -340,12 +339,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User admin", func(t *testing.T) { t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
@ -380,24 +379,24 @@ func TestProject(t *testing.T) {
t.Run("Empty title", func(t *testing.T) { t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`) _, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required") assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
}) })
t.Run("Title too long", func(t *testing.T) { t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`) _, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)") assert.Contains(t, err.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
}) })
t.Run("Permissions check", func(t *testing.T) { t.Run("Permissions check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`) _, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`) _, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`) rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
@ -419,7 +418,7 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`) _, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`) rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -68,70 +67,70 @@ func TestTaskComments(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "15", "commentid": "3"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "15", "commentid": "3"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "18", "commentid": "6"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "18", "commentid": "6"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) { t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User admin", func(t *testing.T) { t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -151,70 +150,70 @@ func TestTaskComments(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "15", "commentid": "3"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "15", "commentid": "3"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team admin", func(t *testing.T) { t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "18", "commentid": "6"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "18", "commentid": "6"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User admin", func(t *testing.T) { t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) { t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User admin", func(t *testing.T) { t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
}) })
}) })
@ -234,12 +233,12 @@ func TestTaskComments(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "34"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "34"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "15"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "15"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "16"}, `{"comment":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "16"}, `{"comment":"Lorem Ipsum"}`)
@ -255,7 +254,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "18"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "18"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "19"}, `{"comment":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "19"}, `{"comment":"Lorem Ipsum"}`)
@ -271,7 +270,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`)
@ -287,7 +286,7 @@ func TestTaskComments(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)

View File

@ -23,7 +23,6 @@ import (
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/web/handler" "code.vikunja.io/api/pkg/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -223,12 +222,12 @@ func TestTask(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`)
@ -244,7 +243,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`)
@ -260,7 +259,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
@ -276,7 +275,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
@ -323,12 +322,12 @@ func TestTask(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) { t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "16"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "16"})
@ -344,7 +343,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "19"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "19"})
@ -360,7 +359,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
@ -376,7 +375,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"}) _, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"}) rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
@ -406,12 +405,12 @@ func TestTask(t *testing.T) {
// Owned by user13 // Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team readonly", func(t *testing.T) { t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Team write", func(t *testing.T) { t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
@ -427,7 +426,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via User readonly", func(t *testing.T) { t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via User write", func(t *testing.T) { t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
@ -443,7 +442,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) { t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project Team write", func(t *testing.T) { t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
@ -459,7 +458,7 @@ func TestTask(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) { t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`) _, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
require.Error(t, err) require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`) assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
}) })
t.Run("Shared Via Parent Project User write", func(t *testing.T) { t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`) rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)

View File

@ -23,7 +23,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1" apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -37,7 +36,7 @@ func TestUserConfirmEmail(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) { t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`, nil, nil) _, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`, nil, nil)
require.Error(t, err) require.Error(t, err)
assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code) assert.Equal(t, http.StatusPreconditionFailed, getHTTPErrorCode(err))
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken) assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
}) })
t.Run("Empty token", func(t *testing.T) { t.Run("Empty token", func(t *testing.T) {

View File

@ -22,7 +22,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1" apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -33,8 +32,8 @@ func TestUserExportDownload(t *testing.T) {
body := `{"password": "12345678"}` body := `{"password": "12345678"}`
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser15, body, nil, nil) _, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser15, body, nil, nil)
require.Error(t, err) require.Error(t, err)
assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code) assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found") assert.Contains(t, getHTTPErrorMessage(err), "No user data export found")
}) })
t.Run("export file metadata exists but physical file does not exist", func(t *testing.T) { t.Run("export file metadata exists but physical file does not exist", func(t *testing.T) {
@ -43,7 +42,7 @@ func TestUserExportDownload(t *testing.T) {
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser1, body, nil, nil) _, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser1, body, nil, nil)
require.Error(t, err) require.Error(t, err)
// This should return 404 when the physical file doesn't exist // This should return 404 when the physical file doesn't exist
assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code) assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found") assert.Contains(t, getHTTPErrorMessage(err), "No user data export found")
}) })
} }

View File

@ -22,7 +22,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1" apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -41,7 +40,7 @@ func TestUserRequestResetPasswordToken(t *testing.T) {
t.Run("Invalid email address", func(t *testing.T) { t.Run("Invalid email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`, nil, nil) _, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`, nil, nil)
require.Error(t, err) require.Error(t, err)
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code) assert.Equal(t, http.StatusBadRequest, getHTTPErrorCode(err))
}) })
t.Run("No user with that email address", func(t *testing.T) { t.Run("No user with that email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`, nil, nil) _, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`, nil, nil)

View File

@ -23,7 +23,6 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1" apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user" "code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -40,7 +39,7 @@ func TestUserPasswordReset(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) { t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`, nil, nil) _, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`, nil, nil)
require.Error(t, err) require.Error(t, err)
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code) assert.Equal(t, http.StatusBadRequest, getHTTPErrorCode(err))
}) })
t.Run("No new password", func(t *testing.T) { t.Run("No new password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{ _, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{