Files
vikunja/pkg/models/error_i18n_test.go
kolaente 1f79a8e01a fix(migration): reject repeated csv column mappings, cap labels per row
- Reject a config that maps a column more than once, already when the
  import is started or previewed.
- Import repeated labels of a row once and reject rows with more than
  MaxLabelsPerTask (100) labels.
- Count items while converting rows so a file over the import item limit
  fails before the whole structure is built.
- Walk mappings sorted by column so a row only visits the cells it has.
2026-11-05 15:42:45 +01:00

133 lines
4.0 KiB
Go

// 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/>.
// External test package so errors from packages importing models can be covered.
package models_test
import (
"encoding/json"
"os"
"regexp"
"strconv"
"testing"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/web"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var i18nPlaceholderPattern = regexp.MustCompile(`\{(\w+)}`)
func loadI18nErrorMessages(t *testing.T) map[string]string {
t.Helper()
raw, err := os.ReadFile("../../frontend/src/i18n/lang/en.json")
require.NoError(t, err)
var lang struct {
Error map[string]string `json:"error"`
}
require.NoError(t, json.Unmarshal(raw, &lang))
return lang.Error
}
func placeholderNames(message string) map[string]bool {
names := map[string]bool{}
for _, match := range i18nPlaceholderPattern.FindAllStringSubmatch(message, -1) {
names[match[1]] = true
}
return names
}
// I18nParams keys must equal the {placeholder} names in frontend/src/i18n/lang/en.json.
func TestI18nParamsContract(t *testing.T) {
i18nMessages := loadI18nErrorMessages(t)
tests := []struct {
name string
err web.HTTPErrorProcessor
expectedCode int
}{
{
name: "ErrInvalidTimezone",
err: models.ErrInvalidTimezone{Name: "x"},
expectedCode: models.ErrCodeInvalidTimezone,
},
{
name: "ErrInvalidAPITokenPermission",
err: &models.ErrInvalidAPITokenPermission{Group: "g", Permission: "p"},
expectedCode: models.ErrCodeInvalidAPITokenPermission,
},
{
name: "user.ErrInvalidClaimData",
err: &user.ErrInvalidClaimData{Field: "f", Type: "t"},
expectedCode: user.ErrCodeInvalidClaimData,
},
{
name: "user.ErrInvalidTimezone",
err: user.ErrInvalidTimezone{Name: "x"},
expectedCode: user.ErrorCodeInvalidTimezone,
},
{
name: "migration.ErrCSVDuplicateColumnMapping",
err: &migration.ErrCSVDuplicateColumnMapping{Column: 1},
expectedCode: migration.ErrCodeCSVDuplicateColumnMapping,
},
{
name: "migration.ErrCSVTooManyLabels",
err: &migration.ErrCSVTooManyLabels{Row: 1, MaxLabels: 1},
expectedCode: migration.ErrCodeCSVTooManyLabels,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
httpErr := tt.err.HTTPError()
assert.Equal(t, tt.expectedCode, httpErr.Code)
message, ok := i18nMessages[strconv.Itoa(tt.expectedCode)]
require.True(t, ok, "no en.json error message found for code %d", tt.expectedCode)
expectedParams := placeholderNames(message)
actualParams := map[string]bool{}
for key := range httpErr.I18nParams {
actualParams[key] = true
}
assert.Equal(t, expectedParams, actualParams)
})
}
coveredCodes := map[string]bool{}
for _, tt := range tests {
coveredCodes[strconv.Itoa(tt.expectedCode)] = true
}
parametrisedCodes := map[string]bool{}
for code, message := range i18nMessages {
if len(placeholderNames(message)) > 0 {
parametrisedCodes[code] = true
}
}
assert.Equal(t, parametrisedCodes, coveredCodes, "every parametrised en.json error message must have a corresponding I18nParams test case")
}