snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
This commit is contained in:
397
server/public/model/property_field_attrs_validation_test.go
Normal file
397
server/public/model/property_field_attrs_validation_test.go
Normal file
@@ -0,0 +1,397 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidatePropertyFieldVisibility(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
attrs StringInterface
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "nil attrs", attrs: nil},
|
||||
{name: "no visibility key", attrs: StringInterface{"other": "val"}},
|
||||
{name: "empty string", attrs: StringInterface{PropertyFieldAttrVisibility: ""}},
|
||||
{name: "hidden", attrs: StringInterface{PropertyFieldAttrVisibility: "hidden"}},
|
||||
{name: "when_set", attrs: StringInterface{PropertyFieldAttrVisibility: "when_set"}},
|
||||
{name: "always", attrs: StringInterface{PropertyFieldAttrVisibility: "always"}},
|
||||
{name: "invalid", attrs: StringInterface{PropertyFieldAttrVisibility: "public"}, wantErr: true},
|
||||
{name: "non-string type", attrs: StringInterface{PropertyFieldAttrVisibility: 42}, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := &PropertyField{Attrs: tt.attrs}
|
||||
err := ValidatePropertyFieldVisibility(field)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePropertyFieldSortOrder(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
attrs StringInterface
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "nil attrs", attrs: nil},
|
||||
{name: "no sort_order key", attrs: StringInterface{"other": "val"}},
|
||||
{name: "float64", attrs: StringInterface{PropertyFieldAttrSortOrder: float64(1.5)}},
|
||||
{name: "int", attrs: StringInterface{PropertyFieldAttrSortOrder: 1}},
|
||||
{name: "int64", attrs: StringInterface{PropertyFieldAttrSortOrder: int64(42)}},
|
||||
{name: "json.Number", attrs: StringInterface{PropertyFieldAttrSortOrder: json.Number("3.14")}},
|
||||
{name: "string", attrs: StringInterface{PropertyFieldAttrSortOrder: "not_a_number"}, wantErr: true},
|
||||
{name: "bool", attrs: StringInterface{PropertyFieldAttrSortOrder: true}, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := &PropertyField{Attrs: tt.attrs}
|
||||
err := ValidatePropertyFieldSortOrder(field)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeAndValidatePropertyFieldBoolAttr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
attrs StringInterface
|
||||
want bool
|
||||
wantKey bool
|
||||
wantError string
|
||||
}{
|
||||
{name: "nil attrs", key: PropertyFieldAttrRequired, attrs: nil},
|
||||
{name: "no key", key: PropertyFieldAttrRequired, attrs: StringInterface{"other": "value"}},
|
||||
{name: "explicit nil clears the key", key: PropertyFieldAttrRequired, attrs: StringInterface{PropertyFieldAttrRequired: nil}},
|
||||
{name: "required true", key: PropertyFieldAttrRequired, attrs: StringInterface{PropertyFieldAttrRequired: true}, want: true, wantKey: true},
|
||||
{name: "required false is kept", key: PropertyFieldAttrRequired, attrs: StringInterface{PropertyFieldAttrRequired: false}, want: false, wantKey: true},
|
||||
{name: "editable false is kept", key: PropertyFieldAttrEditable, attrs: StringInterface{PropertyFieldAttrEditable: false}, want: false, wantKey: true},
|
||||
{name: "string is not coerced", key: PropertyFieldAttrRequired, attrs: StringInterface{PropertyFieldAttrRequired: "true"}, wantError: "required must be a boolean"},
|
||||
{name: "numeric is rejected", key: PropertyFieldAttrEditable, attrs: StringInterface{PropertyFieldAttrEditable: 1}, wantError: "editable must be a boolean"},
|
||||
{name: "array is rejected", key: PropertyFieldAttrRequired, attrs: StringInterface{PropertyFieldAttrRequired: []any{true}}, wantError: "required must be a boolean"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := &PropertyField{Attrs: tt.attrs}
|
||||
err := SanitizeAndValidatePropertyFieldBoolAttr(field, tt.key)
|
||||
if tt.wantError != "" {
|
||||
require.ErrorContains(t, err, tt.wantError)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if field.Attrs == nil {
|
||||
return
|
||||
}
|
||||
actual, ok := field.Attrs[tt.key]
|
||||
require.Equal(t, tt.wantKey, ok)
|
||||
if tt.wantKey {
|
||||
require.Equal(t, tt.want, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The two keys are independent: validating one must not disturb the other, and
|
||||
// neither may touch a key the hook doesn't own.
|
||||
func TestSanitizeAndValidatePropertyFieldBoolAttrLeavesOtherKeys(t *testing.T) {
|
||||
field := &PropertyField{Attrs: StringInterface{
|
||||
PropertyFieldAttrRequired: true,
|
||||
PropertyFieldAttrEditable: nil,
|
||||
PropertyFieldAttrSortOrder: 3,
|
||||
PropertyFieldAttrVisibility: PropertyFieldVisibilityAlways,
|
||||
}}
|
||||
|
||||
require.NoError(t, SanitizeAndValidatePropertyFieldBoolAttr(field, PropertyFieldAttrRequired))
|
||||
require.NoError(t, SanitizeAndValidatePropertyFieldBoolAttr(field, PropertyFieldAttrEditable))
|
||||
|
||||
require.Equal(t, true, field.Attrs[PropertyFieldAttrRequired])
|
||||
require.NotContains(t, field.Attrs, PropertyFieldAttrEditable)
|
||||
require.Equal(t, 3, field.Attrs[PropertyFieldAttrSortOrder])
|
||||
require.Equal(t, PropertyFieldVisibilityAlways, field.Attrs[PropertyFieldAttrVisibility])
|
||||
}
|
||||
|
||||
func TestIsPropertyFieldRequired(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
field *PropertyField
|
||||
want bool
|
||||
}{
|
||||
{name: "nil field", field: nil, want: false},
|
||||
{name: "nil attrs", field: &PropertyField{}, want: false},
|
||||
{name: "absent key", field: &PropertyField{Attrs: StringInterface{}}, want: false},
|
||||
{name: "true", field: &PropertyField{Attrs: StringInterface{PropertyFieldAttrRequired: true}}, want: true},
|
||||
{name: "false", field: &PropertyField{Attrs: StringInterface{PropertyFieldAttrRequired: false}}, want: false},
|
||||
// A typo must not silently block channel creation.
|
||||
{name: "stringly true", field: &PropertyField{Attrs: StringInterface{PropertyFieldAttrRequired: "true"}}, want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, IsPropertyFieldRequired(tt.field))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmptyPropertyValue(t *testing.T) {
|
||||
empty := []string{"", " ", "null", `""`, "[]", " [] "}
|
||||
for _, raw := range empty {
|
||||
require.True(t, IsEmptyPropertyValue(json.RawMessage(raw)), raw)
|
||||
}
|
||||
|
||||
set := []string{`"x"`, `["a"]`, "0", "false", `{"a":1}`}
|
||||
for _, raw := range set {
|
||||
require.False(t, IsEmptyPropertyValue(json.RawMessage(raw)), raw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeAndValidatePropertyFieldChangePolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fieldType PropertyFieldType
|
||||
attrs StringInterface
|
||||
want string
|
||||
wantKey bool
|
||||
wantError string
|
||||
}{
|
||||
{name: "nil attrs", fieldType: PropertyFieldTypeSelect, attrs: nil},
|
||||
{name: "no key", fieldType: PropertyFieldTypeSelect, attrs: StringInterface{"other": "value"}},
|
||||
{name: "explicit nil clears the key", fieldType: PropertyFieldTypeSelect, attrs: StringInterface{PropertyFieldAttrChangePolicy: nil}},
|
||||
{name: "empty clears the key", fieldType: PropertyFieldTypeSelect, attrs: StringInterface{PropertyFieldAttrChangePolicy: " "}},
|
||||
{name: "any is the default and is not stored", fieldType: PropertyFieldTypeSelect, attrs: StringInterface{PropertyFieldAttrChangePolicy: PropertyFieldChangePolicyAny}},
|
||||
{
|
||||
name: "never is kept on any type",
|
||||
fieldType: PropertyFieldTypeText,
|
||||
attrs: StringInterface{PropertyFieldAttrChangePolicy: PropertyFieldChangePolicyNever},
|
||||
want: PropertyFieldChangePolicyNever,
|
||||
wantKey: true,
|
||||
},
|
||||
{
|
||||
name: "raise_only is kept on a rank field",
|
||||
fieldType: PropertyFieldTypeRank,
|
||||
attrs: StringInterface{PropertyFieldAttrChangePolicy: " raise_only "},
|
||||
want: PropertyFieldChangePolicyRaiseOnly,
|
||||
wantKey: true,
|
||||
},
|
||||
{
|
||||
name: "raise_only is stripped off a select field, which has no ranks to compare",
|
||||
fieldType: PropertyFieldTypeSelect,
|
||||
attrs: StringInterface{PropertyFieldAttrChangePolicy: PropertyFieldChangePolicyRaiseOnly},
|
||||
},
|
||||
{
|
||||
name: "unknown policy is rejected",
|
||||
fieldType: PropertyFieldTypeRank,
|
||||
attrs: StringInterface{PropertyFieldAttrChangePolicy: "frozen"},
|
||||
wantError: `invalid change_policy "frozen"`,
|
||||
},
|
||||
{
|
||||
name: "non-string is rejected",
|
||||
fieldType: PropertyFieldTypeRank,
|
||||
attrs: StringInterface{PropertyFieldAttrChangePolicy: true},
|
||||
wantError: "change_policy must be a string",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := &PropertyField{Type: tt.fieldType, Attrs: tt.attrs}
|
||||
err := SanitizeAndValidatePropertyFieldChangePolicy(field)
|
||||
if tt.wantError != "" {
|
||||
require.ErrorContains(t, err, tt.wantError)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if field.Attrs == nil {
|
||||
return
|
||||
}
|
||||
actual, ok := field.Attrs[PropertyFieldAttrChangePolicy]
|
||||
require.Equal(t, tt.wantKey, ok)
|
||||
if tt.wantKey {
|
||||
require.Equal(t, tt.want, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// editable predates change_policy, so a field carrying only editable=false must
|
||||
// still read as locked.
|
||||
func TestGetPropertyFieldChangePolicy(t *testing.T) {
|
||||
require.Equal(t, PropertyFieldChangePolicyAny, GetPropertyFieldChangePolicy(&PropertyField{}))
|
||||
require.Equal(t, PropertyFieldChangePolicyAny, GetPropertyFieldChangePolicy(&PropertyField{Attrs: StringInterface{PropertyFieldAttrEditable: true}}))
|
||||
require.Equal(t, PropertyFieldChangePolicyNever, GetPropertyFieldChangePolicy(&PropertyField{Attrs: StringInterface{PropertyFieldAttrEditable: false}}))
|
||||
require.Equal(t, PropertyFieldChangePolicyRaiseOnly, GetPropertyFieldChangePolicy(&PropertyField{Attrs: StringInterface{
|
||||
PropertyFieldAttrChangePolicy: PropertyFieldChangePolicyRaiseOnly,
|
||||
}}))
|
||||
|
||||
// change_policy wins: it is the more expressive key, and the pair is only ever
|
||||
// written together for "never".
|
||||
require.Equal(t, PropertyFieldChangePolicyNever, GetPropertyFieldChangePolicy(&PropertyField{Attrs: StringInterface{
|
||||
PropertyFieldAttrChangePolicy: PropertyFieldChangePolicyNever,
|
||||
PropertyFieldAttrEditable: false,
|
||||
}}))
|
||||
}
|
||||
|
||||
func TestSanitizeAndValidatePropertyFieldActions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
attrs StringInterface
|
||||
want []any
|
||||
wantKey bool
|
||||
wantError string
|
||||
}{
|
||||
{name: "nil attrs", attrs: nil},
|
||||
{name: "no actions key", attrs: StringInterface{"other": "value"}},
|
||||
{name: "nil actions", attrs: StringInterface{PropertyFieldAttrActions: nil}},
|
||||
{name: "empty typed actions", attrs: StringInterface{PropertyFieldAttrActions: []string{}}},
|
||||
{name: "empty decoded actions", attrs: StringInterface{PropertyFieldAttrActions: []any{}}},
|
||||
{
|
||||
name: "classification banner actions",
|
||||
attrs: StringInterface{PropertyFieldAttrActions: []any{PropertyFieldActionDisplayBannerTop, PropertyFieldActionDisplayBannerBottom}},
|
||||
want: []any{PropertyFieldActionDisplayBannerTop, PropertyFieldActionDisplayBannerBottom},
|
||||
wantKey: true,
|
||||
},
|
||||
{
|
||||
name: "smart label actions are trimmed and canonicalized",
|
||||
attrs: StringInterface{PropertyFieldAttrActions: []string{" " + PropertyFieldActionDisplayLabelHeader + " ", PropertyFieldActionDisplayLabelInfo}},
|
||||
want: []any{PropertyFieldActionDisplayLabelHeader, PropertyFieldActionDisplayLabelInfo},
|
||||
wantKey: true,
|
||||
},
|
||||
{name: "non-array", attrs: StringInterface{PropertyFieldAttrActions: "display_label_header"}, wantError: "actions must be an array"},
|
||||
{name: "non-string element", attrs: StringInterface{PropertyFieldAttrActions: []any{42}}, wantError: "actions[0] must be a string"},
|
||||
{name: "empty action", attrs: StringInterface{PropertyFieldAttrActions: []any{" "}}, wantError: "actions must not contain empty strings"},
|
||||
{name: "unknown action", attrs: StringInterface{PropertyFieldAttrActions: []any{"unknown"}}, wantError: "unknown action"},
|
||||
{
|
||||
name: "duplicate action",
|
||||
attrs: StringInterface{PropertyFieldAttrActions: []any{PropertyFieldActionDisplayLabelHeader, PropertyFieldActionDisplayLabelHeader}},
|
||||
wantError: "duplicate action",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
field := &PropertyField{Attrs: tt.attrs}
|
||||
err := SanitizeAndValidatePropertyFieldActions(field)
|
||||
if tt.wantError != "" {
|
||||
require.ErrorContains(t, err, tt.wantError)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if field.Attrs == nil {
|
||||
return
|
||||
}
|
||||
actual, ok := field.Attrs[PropertyFieldAttrActions]
|
||||
require.Equal(t, tt.wantKey, ok)
|
||||
if tt.wantKey && tt.want != nil {
|
||||
require.Equal(t, tt.want, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePropertyValueForValueType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
valueType string
|
||||
value string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "empty value type", valueType: "", value: `"anything"`},
|
||||
{name: "valid email", valueType: "email", value: `"test@example.com"`},
|
||||
{name: "invalid email", valueType: "email", value: `"not-an-email"`, wantErr: true},
|
||||
{name: "empty email string", valueType: "email", value: `""`},
|
||||
{name: "valid url", valueType: "url", value: `"https://example.com"`},
|
||||
{name: "valid url with path", valueType: "url", value: `"https://example.com/path?q=1"`},
|
||||
{name: "invalid url - plain string", valueType: "url", value: `"not a url"`, wantErr: true},
|
||||
{name: "invalid url - relative path", valueType: "url", value: `"/relative/path"`, wantErr: true},
|
||||
{name: "invalid url - missing host", valueType: "url", value: `"http://"`, wantErr: true},
|
||||
{name: "invalid url - missing scheme", valueType: "url", value: `"example.com"`, wantErr: true},
|
||||
{name: "phone (any string)", valueType: "phone", value: `"+1-555-0123"`},
|
||||
{name: "unknown value type", valueType: "fax", value: `"test"`, wantErr: true},
|
||||
{name: "non-string json", valueType: "email", value: `42`, wantErr: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidatePropertyValueForValueType(tt.valueType, json.RawMessage(tt.value))
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPropertyFieldSynced(t *testing.T) {
|
||||
assert.False(t, IsPropertyFieldSynced(&PropertyField{}))
|
||||
assert.False(t, IsPropertyFieldSynced(&PropertyField{Attrs: StringInterface{}}))
|
||||
assert.True(t, IsPropertyFieldSynced(&PropertyField{Attrs: StringInterface{PropertyFieldAttrLDAP: "attr"}}))
|
||||
assert.True(t, IsPropertyFieldSynced(&PropertyField{Attrs: StringInterface{PropertyFieldAttrSAML: "attr"}}))
|
||||
assert.True(t, IsPropertyFieldSynced(&PropertyField{Attrs: StringInterface{PropertyFieldAttrLDAP: "a", PropertyFieldAttrSAML: "b"}}))
|
||||
}
|
||||
|
||||
func TestGetPropertyFieldSyncSource(t *testing.T) {
|
||||
assert.Equal(t, "", GetPropertyFieldSyncSource(&PropertyField{}))
|
||||
assert.Equal(t, "ldap", GetPropertyFieldSyncSource(&PropertyField{Attrs: StringInterface{PropertyFieldAttrLDAP: "attr"}}))
|
||||
assert.Equal(t, "saml", GetPropertyFieldSyncSource(&PropertyField{Attrs: StringInterface{PropertyFieldAttrSAML: "attr"}}))
|
||||
// ldap takes priority
|
||||
assert.Equal(t, "ldap", GetPropertyFieldSyncSource(&PropertyField{Attrs: StringInterface{PropertyFieldAttrLDAP: "a", PropertyFieldAttrSAML: "b"}}))
|
||||
}
|
||||
|
||||
func TestIsValidPropertyFieldVisibility(t *testing.T) {
|
||||
assert.True(t, IsValidPropertyFieldVisibility("hidden"))
|
||||
assert.True(t, IsValidPropertyFieldVisibility("when_set"))
|
||||
assert.True(t, IsValidPropertyFieldVisibility("always"))
|
||||
assert.False(t, IsValidPropertyFieldVisibility(""))
|
||||
assert.False(t, IsValidPropertyFieldVisibility("public"))
|
||||
}
|
||||
|
||||
func TestIsValidPropertyFieldValueType(t *testing.T) {
|
||||
assert.True(t, IsValidPropertyFieldValueType("email"))
|
||||
assert.True(t, IsValidPropertyFieldValueType("url"))
|
||||
assert.True(t, IsValidPropertyFieldValueType("phone"))
|
||||
assert.False(t, IsValidPropertyFieldValueType(""))
|
||||
assert.False(t, IsValidPropertyFieldValueType("fax"))
|
||||
}
|
||||
|
||||
func TestGetPropertyFieldValueType(t *testing.T) {
|
||||
assert.Equal(t, "", GetPropertyFieldValueType(&PropertyField{}))
|
||||
assert.Equal(t, "", GetPropertyFieldValueType(&PropertyField{Attrs: StringInterface{}}))
|
||||
assert.Equal(t, "email", GetPropertyFieldValueType(&PropertyField{Attrs: StringInterface{PropertyFieldAttrValueType: "email"}}))
|
||||
assert.Equal(t, "email", GetPropertyFieldValueType(&PropertyField{Attrs: StringInterface{PropertyFieldAttrValueType: " email "}}))
|
||||
}
|
||||
|
||||
func TestCallerIDConstants(t *testing.T) {
|
||||
require.NotEmpty(t, CallerIDLDAPSync)
|
||||
require.NotEmpty(t, CallerIDSAMLSync)
|
||||
require.NotEqual(t, CallerIDLDAPSync, CallerIDSAMLSync)
|
||||
|
||||
// The sync caller IDs must not be valid plugin IDs, otherwise an
|
||||
// admin-installed plugin could set its manifest ID to one of these
|
||||
// values and bypass the sync-lock check for LDAP/SAML-managed fields.
|
||||
require.False(t, IsValidPluginId(CallerIDLDAPSync),
|
||||
"CallerIDLDAPSync must not be a valid plugin ID")
|
||||
require.False(t, IsValidPluginId(CallerIDSAMLSync),
|
||||
"CallerIDSAMLSync must not be a valid plugin ID")
|
||||
}
|
||||
Reference in New Issue
Block a user