mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-02-01 22:47:40 +00:00
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:
@@ -17,6 +17,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -110,6 +111,26 @@ func (err ValidationHTTPError) Error() string {
|
||||
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 {
|
||||
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
|
||||
// ===========
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/modules/avatar/upload"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
petname "github.com/dustinkirkland/golang-petname"
|
||||
@@ -140,12 +139,12 @@ func HandleCallback(c echo.Context) error {
|
||||
"details": detailedErr.Details,
|
||||
})
|
||||
}
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
cl, err := getClaims(provider, oauthToken, idToken)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -156,21 +155,21 @@ func HandleCallback(c echo.Context) error {
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Errorf("Error creating new user for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
teamData := getTeamDataFromToken(cl.VikunjaGroups, provider)
|
||||
|
||||
err = models.SyncExternalTeamsForUser(s, u, teamData, idToken.Issuer, "OIDC")
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Errorf("Error creating new team for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create token
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/modules/background/unsplash"
|
||||
"code.vikunja.io/api/pkg/modules/background/upload"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/bbrks/go-blurhash"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
@@ -138,7 +137,7 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
|
||||
project, auth, err := bp.setBackgroundPreparations(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
p := bp.Provider()
|
||||
@@ -153,13 +152,13 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
|
||||
err = p.Set(s, image, project, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = project.ReadOne(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get + upload the image
|
||||
@@ -205,7 +204,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
|
||||
mime, err := mimetype.DetectReader(srcf)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(mime.String(), "image") {
|
||||
_ = 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 handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = project.ReadOne(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, auth, handler.HandleHTTPError(err)
|
||||
return nil, auth, err
|
||||
}
|
||||
if !can {
|
||||
_ = s.Rollback()
|
||||
@@ -359,12 +358,12 @@ func GetProjectBackground(c echo.Context) error {
|
||||
}
|
||||
if err := bgFile.LoadFileByID(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
stat, err := bgFile.File.Stat()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
_ = 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
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -56,7 +55,7 @@ func unsplashImage(url string, c echo.Context) error {
|
||||
func ProxyUnsplashImage(c echo.Context) error {
|
||||
photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
pingbackByPhotoID(photo.ID)
|
||||
return unsplashImage(photo.Urls.Raw, c)
|
||||
@@ -76,7 +75,7 @@ func ProxyUnsplashImage(c echo.Context) error {
|
||||
func ProxyUnsplashThumb(c echo.Context) error {
|
||||
photo, err := getUnsplashPhotoInfoByID(c.Param("image"))
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -21,19 +21,18 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func status(ms migration.MigratorName, c echo.Context) error {
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
status, err := migration.GetMigrationStatus(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, status)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -65,12 +64,12 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
|
||||
// Get the user from context
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
stats, err := migration.GetMigrationStatus(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !stats.StartedAt.IsZero() && stats.FinishedAt.IsZero() {
|
||||
@@ -92,7 +91,7 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
|
||||
User: user,
|
||||
})
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Migration was started successfully."})
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -44,7 +43,7 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
|
||||
// Get the user from context
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := c.FormFile("import")
|
||||
@@ -59,18 +58,18 @@ func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
|
||||
|
||||
m, err := migration.StartMigration(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Do the migration
|
||||
err = ms.Migrate(user, src, file.Size)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = migration.FinishMigration(m)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/modules/avatar/empty"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/upload"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -58,7 +57,7 @@ func GetAvatar(c echo.Context) error {
|
||||
u, err := user.GetUserWithEmail(s, &user.User{Username: username})
|
||||
if err != nil && !user.IsErrUserDoesNotExist(err) {
|
||||
log.Errorf("Error getting user for avatar: %v", err)
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
found := err == nil || !user.IsErrUserDoesNotExist(err)
|
||||
@@ -75,7 +74,7 @@ func GetAvatar(c echo.Context) error {
|
||||
sizeInt, err = strconv.ParseInt(size, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing size: %v", err)
|
||||
return handler.HandleHTTPError(err)
|
||||
return models.ErrInvalidModel{Message: "Invalid size parameter"}
|
||||
}
|
||||
}
|
||||
if sizeInt > config.ServiceMaxAvatarSize.GetInt64() {
|
||||
@@ -86,7 +85,7 @@ func GetAvatar(c echo.Context) error {
|
||||
a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt)
|
||||
if err != nil {
|
||||
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)
|
||||
@@ -112,12 +111,12 @@ func UploadAvatar(c echo.Context) (err error) {
|
||||
|
||||
uc, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
u, err := user.GetUserByID(s, uc.ID)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get + upload the image
|
||||
@@ -137,7 +136,7 @@ func UploadAvatar(c echo.Context) (err error) {
|
||||
mime, err := mimetype.DetectReader(src)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(mime.String(), "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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
avatar.FlushAllCaches(u)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -56,7 +55,7 @@ func AuthenticateLinkShare(c echo.Context) error {
|
||||
sh := &LinkShareAuth{}
|
||||
err := c.Bind(sh)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -64,19 +63,19 @@ func AuthenticateLinkShare(c echo.Context) error {
|
||||
|
||||
share, err := models.GetLinkShareByHash(s, sh.Hash)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if share.SharingType == models.SharingTypeWithPassword {
|
||||
err := models.VerifyLinkSharePassword(share, sh.Password)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t, err := auth.NewLinkShareJWTAuthtoken(share)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
share.Password = ""
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/modules/auth/ldap"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"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())
|
||||
if err != nil && !user2.IsErrWrongUsernameOrPassword(err) {
|
||||
_ = 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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if user.Status == user2.StatusDisabled {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(&user2.ErrAccountDisabled{UserID: user.ID})
|
||||
return &user2.ErrAccountDisabled{UserID: user.ID}
|
||||
}
|
||||
|
||||
totpEnabled, err := user2.TOTPEnabledForUser(s, user)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if totpEnabled {
|
||||
if u.TOTPPasscode == "" {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(user2.ErrInvalidTOTPPasscode{})
|
||||
return user2.ErrInvalidTOTPPasscode{}
|
||||
}
|
||||
|
||||
_, err = user2.ValidateTOTPPasscode(s, &user2.TOTPPasscode{
|
||||
@@ -97,7 +96,7 @@ func Login(c echo.Context) (err error) {
|
||||
user2.HandleFailedTOTPAuth(s, user)
|
||||
}
|
||||
_ = 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 {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create token
|
||||
@@ -141,12 +140,12 @@ func RenewToken(c echo.Context) (err error) {
|
||||
err := share.ReadOne(s, share)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
t, err := auth.NewLinkShareJWTAuthtoken(share)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var long bool
|
||||
|
||||
@@ -27,11 +27,33 @@ import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
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"
|
||||
)
|
||||
|
||||
// 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
|
||||
// @Summary Upload a task attachment
|
||||
// @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
|
||||
auth, err := auth2.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -65,7 +87,7 @@ func UploadTaskAttachment(c echo.Context) error {
|
||||
can, err := taskAttachment.CanCreate(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !can {
|
||||
return echo.ErrForbidden
|
||||
@@ -78,11 +100,11 @@ func UploadTaskAttachment(c echo.Context) error {
|
||||
if errors.Is(err, http.ErrNotMultipart) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No multipart form provided")
|
||||
}
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
type result struct {
|
||||
Errors []*echo.HTTPError `json:"errors"`
|
||||
Errors []attachmentUploadError `json:"errors"`
|
||||
Success []*models.TaskAttachment `json:"success"`
|
||||
}
|
||||
r := &result{}
|
||||
@@ -95,14 +117,14 @@ func UploadTaskAttachment(c echo.Context) error {
|
||||
|
||||
f, err := file.Open()
|
||||
if err != nil {
|
||||
r.Errors = append(r.Errors, handler.HandleHTTPError(err))
|
||||
r.Errors = append(r.Errors, toAttachmentUploadError(err))
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = ta.NewAttachment(s, f, file.Filename, uint64(file.Size), auth)
|
||||
if err != nil {
|
||||
r.Errors = append(r.Errors, handler.HandleHTTPError(err))
|
||||
r.Errors = append(r.Errors, toAttachmentUploadError(err))
|
||||
continue
|
||||
}
|
||||
r.Success = append(r.Success, ta)
|
||||
@@ -110,7 +132,7 @@ func UploadTaskAttachment(c echo.Context) error {
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, r)
|
||||
@@ -140,7 +162,7 @@ func GetTaskAttachment(c echo.Context) error {
|
||||
// Permissions check
|
||||
auth, err := auth2.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -149,7 +171,7 @@ func GetTaskAttachment(c echo.Context) error {
|
||||
can, _, err := taskAttachment.CanRead(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !can {
|
||||
return echo.ErrForbidden
|
||||
@@ -159,7 +181,7 @@ func GetTaskAttachment(c echo.Context) error {
|
||||
err = taskAttachment.ReadOne(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if config.FilesType.GetString() == "s3" {
|
||||
// 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
|
||||
_, err = io.Copy(c.Response().Writer, taskAttachment.File.File)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -43,12 +42,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
|
||||
|
||||
u, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := user.GenerateNewCaldavToken(u)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, token)
|
||||
@@ -69,12 +68,12 @@ func GenerateCaldavToken(c echo.Context) (err error) {
|
||||
func GetCaldavTokens(c echo.Context) error {
|
||||
u, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
tokens, err := user.GetCaldavTokens(u)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, tokens)
|
||||
@@ -95,17 +94,17 @@ func GetCaldavTokens(c echo.Context) error {
|
||||
func DeleteCaldavToken(c echo.Context) error {
|
||||
u, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = user.DeleteCaldavTokenByID(u, id)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, &models.Message{Message: "The token was deleted successfully."})
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -51,12 +50,12 @@ func UserConfirmEmail(c echo.Context) error {
|
||||
err := user.ConfirmEmail(s, &emailConfirm)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "The email was confirmed successfully."})
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -53,13 +52,13 @@ func UserRequestDeletion(c echo.Context) error {
|
||||
|
||||
err := s.Begin()
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := user.GetCurrentUserFromDB(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if u.IsLocalUser() {
|
||||
@@ -76,20 +75,20 @@ func UserRequestDeletion(c echo.Context) error {
|
||||
err = user.CheckUserPassword(u, deletionRequest.Password)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err).SetInternal(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = user.RequestDeletion(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested deletion."})
|
||||
@@ -122,25 +121,25 @@ func UserConfirmDeletion(c echo.Context) error {
|
||||
|
||||
err = s.Begin()
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := user.GetCurrentUserFromDB(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = user.ConfirmDeletion(s, u, deleteConfirmation.Token)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
u, err := user.GetCurrentUserFromDB(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if u.IsLocalUser() {
|
||||
@@ -187,20 +186,20 @@ func UserCancelDeletion(c echo.Context) error {
|
||||
err = user.CheckUserPassword(u, deletionRequest.Password)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = user.CancelDeletion(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusNoContent, models.Message{Message: "Successfully confirmed the deletion request."})
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -37,13 +36,13 @@ func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err erro
|
||||
|
||||
err = s.Begin()
|
||||
if err != nil {
|
||||
return nil, nil, handler.HandleHTTPError(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
u, err = user.GetCurrentUserFromDB(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, nil, handler.HandleHTTPError(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, nil, handler.HandleHTTPError(err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return
|
||||
@@ -92,13 +91,13 @@ func RequestUserDataExport(c echo.Context) error {
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = 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."})
|
||||
@@ -125,7 +124,7 @@ func DownloadUserDataExport(c echo.Context) error {
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if user has an export file
|
||||
@@ -141,14 +140,14 @@ func DownloadUserDataExport(c echo.Context) error {
|
||||
if files.IsErrFileDoesNotExist(err) {
|
||||
return exportNotFoundError
|
||||
}
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
err = exportFile.LoadFileByID()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return exportNotFoundError
|
||||
}
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if u.ExportFileID == 0 {
|
||||
@@ -184,7 +183,7 @@ func GetUserExportStatus(c echo.Context) error {
|
||||
|
||||
exportFile := &files.File{ID: u.ExportFileID}
|
||||
if err := exportFile.LoadFileMetaByID(); err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
status := UserExportStatus{
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
auth2 "code.vikunja.io/api/pkg/modules/auth"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -50,13 +49,13 @@ func UserList(c echo.Context) error {
|
||||
currentUser, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
users, err := user.ListUsers(s, search, currentUser, nil)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Obfuscate the mailadresses
|
||||
@@ -93,7 +92,7 @@ func ListUsersForProject(c echo.Context) error {
|
||||
project := models.Project{ID: projectID}
|
||||
auth, err := auth2.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -102,7 +101,7 @@ func ListUsersForProject(c echo.Context) error {
|
||||
canRead, _, err := project.CanRead(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !canRead {
|
||||
return echo.ErrForbidden
|
||||
@@ -111,19 +110,19 @@ func ListUsersForProject(c echo.Context) error {
|
||||
currentUser, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
search := c.QueryParam("s")
|
||||
users, err := models.ListUsersFromProject(s, &project, currentUser, search)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, users)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -51,12 +50,12 @@ func UserResetPassword(c echo.Context) error {
|
||||
err := user.ResetPassword(s, &pwReset)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Token was sent."})
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -61,7 +60,7 @@ func RegisterUser(c echo.Context) error {
|
||||
return c.JSON(e.HTTPCode, e)
|
||||
}
|
||||
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if userIn == nil {
|
||||
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 {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create their initial project
|
||||
err = models.CreateNewProjectForUser(s, newUser)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, newUser)
|
||||
|
||||
@@ -28,7 +28,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/avatar"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
)
|
||||
|
||||
// UserAvatarProvider holds the user avatar provider type
|
||||
@@ -81,7 +80,7 @@ func GetUserAvatarProvider(c echo.Context) error {
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -90,12 +89,12 @@ func GetUserAvatarProvider(c echo.Context) error {
|
||||
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
uap := &UserAvatarProvider{AvatarProvider: user.AvatarProvider}
|
||||
@@ -124,7 +123,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -133,7 +132,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
|
||||
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
oldProvider := user.AvatarProvider
|
||||
@@ -143,7 +142,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
|
||||
_, err = user2.UpdateUser(s, user, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if user.AvatarProvider == "initials" {
|
||||
@@ -152,7 +151,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if oldProvider != user.AvatarProvider {
|
||||
@@ -179,9 +178,9 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
||||
if err != nil {
|
||||
var he *echo.HTTPError
|
||||
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)
|
||||
@@ -191,7 +190,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -200,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
||||
user, err := user2.GetUserWithEmail(s, &user2.User{ID: u.ID})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
invalidateAvatar := user.AvatarProvider == "initials" && user.Name != us.Name
|
||||
@@ -220,12 +219,12 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
||||
_, err = user2.UpdateUser(s, user, true)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if invalidateAvatar {
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -63,7 +62,7 @@ func UserShow(c echo.Context) error {
|
||||
|
||||
u, err := models.GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
us := &UserWithSettings{
|
||||
@@ -88,7 +87,7 @@ func UserShow(c echo.Context) error {
|
||||
|
||||
us.AuthProvider, err = getAuthProviderName(u)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, us)
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"xorm.io/xorm"
|
||||
@@ -66,19 +65,19 @@ func getLocalUserFromContext(c echo.Context) (*user.User, *xorm.Session, error)
|
||||
func UserTOTPEnroll(c echo.Context) error {
|
||||
u, s, err := getLocalUserFromContext(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
t, err := user.EnrollTOTP(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, t)
|
||||
@@ -101,7 +100,7 @@ func UserTOTPEnroll(c echo.Context) error {
|
||||
func UserTOTPEnable(c echo.Context) error {
|
||||
u, s, err := getLocalUserFromContext(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
@@ -112,20 +111,20 @@ func UserTOTPEnable(c echo.Context) error {
|
||||
log.Debugf("Invalid model error. Internal error was: %s", err.Error())
|
||||
var he *echo.HTTPError
|
||||
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)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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())
|
||||
var he *echo.HTTPError
|
||||
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)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
err = user.CheckUserPassword(u, login.Password)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = user.DisableTOTP(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = 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
|
||||
@@ -194,26 +193,26 @@ func UserTOTPDisable(c echo.Context) error {
|
||||
func UserTOTPQrCode(c echo.Context) error {
|
||||
u, s, err := getLocalUserFromContext(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
qrcode, err := user.GetTOTPQrCodeForUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
err = jpeg.Encode(buff, qrcode, nil)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
u, s, err := getLocalUserFromContext(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
t, err := user.GetTOTPForUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, t)
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -69,18 +68,18 @@ func UpdateUserEmail(c echo.Context) (err error) {
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = user.UpdateEmail(s, emailUpdate)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = 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."})
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -60,7 +59,7 @@ func UserChangePassword(c echo.Context) error {
|
||||
}
|
||||
|
||||
if newPW.OldPassword == "" {
|
||||
return handler.HandleHTTPError(user.ErrEmptyOldPassword{})
|
||||
return user.ErrEmptyOldPassword{}
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
@@ -69,18 +68,18 @@ func UserChangePassword(c echo.Context) error {
|
||||
// Check the current password
|
||||
if _, err = user.CheckUserCredentials(s, &user.Login{Username: doer.Username, Password: newPW.OldPassword}); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the password
|
||||
if err = user.UpdateUserPassword(s, doer, newPW.NewPassword); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "The password was updated successfully."})
|
||||
|
||||
135
pkg/routes/error_handler.go
Normal file
135
pkg/routes/error_handler.go
Normal 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())
|
||||
}
|
||||
@@ -52,16 +52,13 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"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.)
|
||||
e.Use(middleware.BodyLimit(fmt.Sprintf("%dM", config.GetMaxFileSizeInMBytes()+2)))
|
||||
|
||||
// Set up custom error handler for body limit exceeded when Sentry is not enabled
|
||||
if !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)
|
||||
}
|
||||
}
|
||||
// Set up centralized error handler
|
||||
e.HTTPErrorHandler = CreateHTTPErrorHandler(e, config.SentryEnabled.GetBool())
|
||||
|
||||
return e
|
||||
}
|
||||
@@ -174,36 +162,6 @@ func setupSentry(e *echo.Echo) {
|
||||
e.Use(sentryecho.New(sentryecho.Options{
|
||||
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
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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
|
||||
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
|
||||
@@ -67,7 +68,7 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
|
||||
canCreate, err := currentStruct.CanCreate(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !canCreate {
|
||||
_ = s.Rollback()
|
||||
@@ -79,17 +80,13 @@ func (c *WebHandler) CreateWeb(ctx echo.Context) error {
|
||||
err = currentStruct.Create(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.JSON(http.StatusCreated, currentStruct)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
return err
|
||||
return ctx.JSON(http.StatusCreated, currentStruct)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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
|
||||
@@ -66,7 +67,7 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
|
||||
canDelete, err := currentStruct.CanDelete(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !canDelete {
|
||||
_ = s.Rollback()
|
||||
@@ -77,17 +78,13 @@ func (c *WebHandler) DeleteWeb(ctx echo.Context) error {
|
||||
err = currentStruct.Delete(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.JSON(http.StatusOK, message{"Successfully deleted."})
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
return err
|
||||
return ctx.JSON(http.StatusOK, message{"Successfully deleted."})
|
||||
}
|
||||
|
||||
@@ -17,12 +17,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/web"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// WebHandler defines the webhandler object
|
||||
@@ -36,13 +31,3 @@ type CObject interface {
|
||||
web.CRUDable
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
vconfig "code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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
|
||||
@@ -104,7 +105,7 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
|
||||
result, resultCount, numberOfItems, err := currentStruct.ReadAll(s, currentAuth, search, pageNumber, perPageNumber)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 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{}{}
|
||||
}
|
||||
|
||||
err = ctx.JSON(http.StatusOK, result)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
return err
|
||||
return ctx.JSON(http.StatusOK, result)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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
|
||||
@@ -62,7 +63,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
|
||||
canRead, maxPermission, err := currentStruct.CanRead(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !canRead {
|
||||
_ = s.Rollback()
|
||||
@@ -74,7 +75,7 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
|
||||
err = currentStruct.ReadOne(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the headers
|
||||
@@ -85,12 +86,8 @@ func (c *WebHandler) ReadOneWeb(ctx echo.Context) error {
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.JSON(http.StatusOK, currentStruct)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
return err
|
||||
return ctx.JSON(http.StatusOK, currentStruct)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
|
||||
"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())
|
||||
var he *echo.HTTPError
|
||||
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
|
||||
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
|
||||
@@ -67,7 +68,7 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
|
||||
canUpdate, err := currentStruct.CanUpdate(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
if !canUpdate {
|
||||
_ = s.Rollback()
|
||||
@@ -79,17 +80,13 @@ func (c *WebHandler) UpdateWeb(ctx echo.Context) error {
|
||||
err = currentStruct.Update(s, currentAuth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = ctx.JSON(http.StatusOK, currentStruct)
|
||||
if err != nil {
|
||||
return HandleHTTPError(err)
|
||||
}
|
||||
return err
|
||||
return ctx.JSON(http.StatusOK, currentStruct)
|
||||
}
|
||||
|
||||
150
pkg/webtests/error_responses_test.go
Normal file
150
pkg/webtests/error_responses_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -181,19 +181,60 @@ func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
|
||||
t.Error("Error is nil")
|
||||
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
|
||||
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()
|
||||
}
|
||||
webhttperr, ok := httperr.Message.(web.HTTPError)
|
||||
if !ok {
|
||||
t.Error("Error is not *web.HTTPError")
|
||||
t.Errorf("Error message is not web.HTTPError: %T", httperr.Message)
|
||||
t.FailNow()
|
||||
}
|
||||
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 {
|
||||
user *user.User
|
||||
linkShare *models.LinkSharing
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -90,7 +89,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "4",
|
||||
}, `{"title":""}`)
|
||||
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("Forbidden", func(t *testing.T) {
|
||||
@@ -101,7 +100,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "80",
|
||||
}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{
|
||||
@@ -110,7 +109,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "24",
|
||||
}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
|
||||
@@ -138,7 +137,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "36",
|
||||
}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
|
||||
@@ -166,7 +165,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "48",
|
||||
}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
|
||||
@@ -194,7 +193,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "60",
|
||||
}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{
|
||||
@@ -236,12 +235,12 @@ func TestBucket(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"})
|
||||
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) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
|
||||
@@ -269,7 +268,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "36",
|
||||
})
|
||||
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) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
|
||||
@@ -297,7 +296,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "48",
|
||||
})
|
||||
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) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
|
||||
@@ -325,7 +324,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "60",
|
||||
})
|
||||
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) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{
|
||||
@@ -380,7 +379,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "80",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{
|
||||
@@ -388,7 +387,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "24",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
|
||||
@@ -413,7 +412,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "36",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
|
||||
@@ -438,7 +437,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "48",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
|
||||
@@ -463,7 +462,7 @@ func TestBucket(t *testing.T) {
|
||||
"view": "60",
|
||||
}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -69,34 +68,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":0}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":1}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"permission":2}`)
|
||||
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", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":0}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":1}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"permission":2}`)
|
||||
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) {
|
||||
@@ -113,7 +112,7 @@ func TestLinkSharing(t *testing.T) {
|
||||
t.Run("admin", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"permission":2}`)
|
||||
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) {
|
||||
@@ -197,7 +196,7 @@ func TestLinkSharing(t *testing.T) {
|
||||
// Project 2, not shared with this token
|
||||
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
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) {
|
||||
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
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) {
|
||||
_, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`)
|
||||
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("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
|
||||
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("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
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) {
|
||||
_, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
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) {
|
||||
_, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
|
||||
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("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
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) {
|
||||
_, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
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) {
|
||||
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"})
|
||||
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) {
|
||||
_, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"})
|
||||
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) {
|
||||
_, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"})
|
||||
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) {
|
||||
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
|
||||
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) {
|
||||
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
|
||||
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) {
|
||||
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
|
||||
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("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`)
|
||||
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) {
|
||||
_, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`)
|
||||
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) {
|
||||
_, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`)
|
||||
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) {
|
||||
_, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"})
|
||||
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) {
|
||||
_, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"})
|
||||
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) {
|
||||
_, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
assert.Contains(t, getHTTPErrorMessage(err), `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -89,7 +88,7 @@ func TestProject(t *testing.T) {
|
||||
// Owned by user13
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
|
||||
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"))
|
||||
})
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
|
||||
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) {
|
||||
_, 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)
|
||||
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("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||
@@ -287,17 +286,17 @@ func TestProject(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
|
||||
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) {
|
||||
_, 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)
|
||||
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("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -68,70 +67,70 @@ func TestTaskComments(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "15", "commentid": "3"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "18", "commentid": "6"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "15", "commentid": "3"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "18", "commentid": "6"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
|
||||
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
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "34"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "15"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "18"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/web/handler"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -223,12 +222,12 @@ func TestTask(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"})
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
|
||||
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) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
|
||||
@@ -406,12 +405,12 @@ func TestTask(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
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) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
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) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -37,7 +36,7 @@ func TestUserConfirmEmail(t *testing.T) {
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`, nil, nil)
|
||||
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)
|
||||
})
|
||||
t.Run("Empty token", func(t *testing.T) {
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -33,8 +32,8 @@ func TestUserExportDownload(t *testing.T) {
|
||||
body := `{"password": "12345678"}`
|
||||
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser15, body, nil, nil)
|
||||
require.Error(t, err)
|
||||
assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found")
|
||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||
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) {
|
||||
@@ -43,7 +42,7 @@ func TestUserExportDownload(t *testing.T) {
|
||||
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.DownloadUserDataExport, &testuser1, body, nil, nil)
|
||||
require.Error(t, err)
|
||||
// This should return 404 when the physical file doesn't exist
|
||||
assert.Equal(t, http.StatusNotFound, err.(*echo.HTTPError).Code)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, "No user data export found")
|
||||
assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err))
|
||||
assert.Contains(t, getHTTPErrorMessage(err), "No user data export found")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -41,7 +40,7 @@ func TestUserRequestResetPasswordToken(t *testing.T) {
|
||||
t.Run("Invalid email address", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`, nil, nil)
|
||||
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) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`, nil, nil)
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -40,7 +39,7 @@ func TestUserPasswordReset(t *testing.T) {
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`, nil, nil)
|
||||
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) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{
|
||||
|
||||
Reference in New Issue
Block a user