mattermost-community-enterp.../public/model/custom_profile_attributes_test.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

1136 lines
32 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package model
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewCPAFieldFromPropertyField(t *testing.T) {
tests := []struct {
name string
propertyField *PropertyField
wantAttrs CPAAttrs
wantErr bool
}{
{
name: "valid property field with all attributes",
propertyField: &PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Test Field",
Type: PropertyFieldTypeSelect,
Attrs: StringInterface{
CustomProfileAttributesPropertyAttrsVisibility: CustomProfileAttributesVisibilityAlways,
CustomProfileAttributesPropertyAttrsSortOrder: 1,
CustomProfileAttributesPropertyAttrsValueType: CustomProfileAttributesValueTypeEmail,
PropertyFieldAttributeOptions: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#FF0000",
},
},
},
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
wantAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityAlways,
SortOrder: 1,
ValueType: CustomProfileAttributesValueTypeEmail,
Options: []*CustomProfileAttributesSelectOption{
{
ID: "", // ID will be different in each test run
Name: "Option 1",
Color: "#FF0000",
},
},
},
wantErr: false,
},
{
name: "valid property field with minimal attributes",
propertyField: &PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Test Field",
Type: PropertyFieldTypeText,
Attrs: StringInterface{
CustomProfileAttributesPropertyAttrsVisibility: CustomProfileAttributesVisibilityWhenSet,
CustomProfileAttributesPropertyAttrsSortOrder: 2,
},
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
wantAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityWhenSet,
SortOrder: 2,
ValueType: "",
Options: nil,
},
wantErr: false,
},
{
name: "property field with empty attributes returns empty values",
propertyField: &PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Empty Field",
Type: PropertyFieldTypeText,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
wantAttrs: CPAAttrs{
Visibility: "", // Pure conversion, no defaults (defaults provided by app layer)
SortOrder: 0,
ValueType: "",
Options: nil,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cpaField, err := NewCPAFieldFromPropertyField(tt.propertyField)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
require.NotNil(t, cpaField)
// Check that the PropertyField was copied correctly
assert.Equal(t, tt.propertyField.ID, cpaField.ID)
assert.Equal(t, tt.propertyField.GroupID, cpaField.GroupID)
assert.Equal(t, tt.propertyField.Name, cpaField.Name)
assert.Equal(t, tt.propertyField.Type, cpaField.Type)
// Check that the attributes were parsed correctly
assert.Equal(t, tt.wantAttrs.Visibility, cpaField.Attrs.Visibility)
assert.Equal(t, tt.wantAttrs.SortOrder, cpaField.Attrs.SortOrder)
assert.Equal(t, tt.wantAttrs.ValueType, cpaField.Attrs.ValueType)
// For options, we need to check length since IDs will be different
if tt.wantAttrs.Options != nil {
require.NotNil(t, cpaField.Attrs.Options)
assert.Len(t, cpaField.Attrs.Options, len(tt.wantAttrs.Options))
if len(tt.wantAttrs.Options) > 0 {
assert.Equal(t, tt.wantAttrs.Options[0].Name, cpaField.Attrs.Options[0].Name)
assert.Equal(t, tt.wantAttrs.Options[0].Color, cpaField.Attrs.Options[0].Color)
}
} else {
assert.Nil(t, cpaField.Attrs.Options)
}
})
}
}
func TestCPAFieldToPropertyField(t *testing.T) {
tests := []struct {
name string
cpaField *CPAField
}{
{
name: "convert CPA field with all attributes",
cpaField: &CPAField{
PropertyField: PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Test Field",
Type: PropertyFieldTypeSelect,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
Attrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityAlways,
SortOrder: 1,
ValueType: CustomProfileAttributesValueTypeEmail,
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#FF0000",
},
},
},
},
},
{
name: "convert CPA field with minimal attributes",
cpaField: &CPAField{
PropertyField: PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Test Field",
Type: PropertyFieldTypeText,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
Attrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityWhenSet,
SortOrder: 2,
},
},
},
{
name: "convert CPA field with empty attributes",
cpaField: &CPAField{
PropertyField: PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Empty Field",
Type: PropertyFieldTypeText,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
Attrs: CPAAttrs{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pf := tt.cpaField.ToPropertyField()
require.NotNil(t, pf)
// Check that the PropertyField was copied correctly
assert.Equal(t, tt.cpaField.ID, pf.ID)
assert.Equal(t, tt.cpaField.GroupID, pf.GroupID)
assert.Equal(t, tt.cpaField.Name, pf.Name)
assert.Equal(t, tt.cpaField.Type, pf.Type)
// Check that the attributes were converted correctly
assert.Equal(t, tt.cpaField.Attrs.Visibility, pf.Attrs[CustomProfileAttributesPropertyAttrsVisibility])
assert.Equal(t, tt.cpaField.Attrs.SortOrder, pf.Attrs[CustomProfileAttributesPropertyAttrsSortOrder])
assert.Equal(t, tt.cpaField.Attrs.ValueType, pf.Attrs[CustomProfileAttributesPropertyAttrsValueType])
// Check options
options, ok := pf.Attrs[PropertyFieldAttributeOptions]
if tt.cpaField.Attrs.Options != nil {
require.True(t, ok)
optionsSlice, ok := options.(PropertyOptions[*CustomProfileAttributesSelectOption])
require.True(t, ok)
assert.Len(t, optionsSlice, len(tt.cpaField.Attrs.Options))
}
})
}
// Test managed attribute functionality
t.Run("managed attribute", func(t *testing.T) {
managedTests := []struct {
name string
cpaField *CPAField
}{
{
name: "CPA field with managed attribute should include it in conversion",
cpaField: &CPAField{
PropertyField: PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Managed Field",
Type: PropertyFieldTypeText,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
Attrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityAlways,
SortOrder: 1,
Managed: "admin",
},
},
},
{
name: "CPA field with empty managed attribute should include it in conversion",
cpaField: &CPAField{
PropertyField: PropertyField{
ID: NewId(),
GroupID: CustomProfileAttributesPropertyGroupName,
Name: "Non-managed Field",
Type: PropertyFieldTypeText,
CreateAt: GetMillis(),
UpdateAt: GetMillis(),
},
Attrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityWhenSet,
SortOrder: 2,
Managed: "",
},
},
},
}
for _, tt := range managedTests {
t.Run(tt.name, func(t *testing.T) {
pf := tt.cpaField.ToPropertyField()
require.NotNil(t, pf)
// Check that the PropertyField was copied correctly
assert.Equal(t, tt.cpaField.ID, pf.ID)
assert.Equal(t, tt.cpaField.GroupID, pf.GroupID)
assert.Equal(t, tt.cpaField.Name, pf.Name)
assert.Equal(t, tt.cpaField.Type, pf.Type)
// Check that the managed attribute was converted correctly
assert.Equal(t, tt.cpaField.Attrs.Managed, pf.Attrs[CustomProfileAttributesPropertyAttrsManaged])
})
}
})
}
func TestCustomProfileAttributeSelectOptionIsValid(t *testing.T) {
tests := []struct {
name string
option CustomProfileAttributesSelectOption
wantErr string
}{
{
name: "valid option with color",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: "Test Option",
Color: "#FF0000",
},
wantErr: "",
},
{
name: "valid option without color",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: "Test Option",
},
wantErr: "",
},
{
name: "empty ID",
option: CustomProfileAttributesSelectOption{
ID: "",
Name: "Test Option",
Color: "#FF0000",
},
wantErr: "id cannot be empty",
},
{
name: "invalid ID",
option: CustomProfileAttributesSelectOption{
ID: "invalid-id",
Name: "Test Option",
Color: "#FF0000",
},
wantErr: "id is not a valid ID",
},
{
name: "empty name",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: "",
Color: "#FF0000",
},
wantErr: "name cannot be empty",
},
{
name: "name too long",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: strings.Repeat("a", CPAOptionNameMaxLength+1),
Color: "#FF0000",
},
wantErr: fmt.Sprintf("name is too long, max length is %d", CPAOptionNameMaxLength),
},
{
name: "color too long",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: "Test Option",
Color: strings.Repeat("a", CPAOptionColorMaxLength+1),
},
wantErr: fmt.Sprintf("color is too long, max length is %d", CPAOptionColorMaxLength),
},
{
name: "name exactly at max length",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: strings.Repeat("a", CPAOptionNameMaxLength),
Color: "#FF0000",
},
wantErr: "",
},
{
name: "color exactly at max length",
option: CustomProfileAttributesSelectOption{
ID: NewId(),
Name: "Test Option",
Color: strings.Repeat("a", CPAOptionColorMaxLength),
},
wantErr: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.option.IsValid()
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
func TestCPAField_SanitizeAndValidate(t *testing.T) {
tests := []struct {
name string
field *CPAField
expectError bool
errorId string
expectedAttrs CPAAttrs
checkOptionsID bool
}{
{
name: "valid text field with no value type",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: "when_set",
},
},
{
name: "valid text field with valid value type and whitespace",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
ValueType: " email ",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: "when_set",
ValueType: CustomProfileAttributesValueTypeEmail,
},
},
{
name: "valid text field with visibility and whitespace",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Visibility: " hidden ",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityHidden,
},
},
{
name: "invalid text field with invalid value type",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
ValueType: "invalid_type",
},
},
expectError: true,
errorId: "app.custom_profile_attributes.sanitize_and_validate.app_error",
},
{
name: "valid select field with valid options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeSelect,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
Name: "Option 1",
Color: "#123456",
},
{
Name: "Option 2",
Color: "#654321",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{Name: "Option 1", Color: "#123456"},
{Name: "Option 2", Color: "#654321"},
},
},
},
{
name: "valid select field with valid options with ids",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeSelect,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: "t9ceh651eir4zkhyh4m54s5r7w",
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{ID: "t9ceh651eir4zkhyh4m54s5r7w", Name: "Option 1", Color: "#123456"},
},
},
checkOptionsID: true,
},
{
name: "invalid select field with duplicate option names",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeSelect,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
Name: "Option 1",
Color: "opt1",
},
{
Name: "Option 1",
Color: "opt2",
},
},
},
},
expectError: true,
errorId: "app.custom_profile_attributes.sanitize_and_validate.app_error",
},
{
name: "invalid field with unknown visibility",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Visibility: "unknown",
},
},
expectError: true,
errorId: "app.custom_profile_attributes.sanitize_and_validate.app_error",
},
// Test options cleaning for types that don't support options
{
name: "text field with options should clean options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: nil, // Options should be cleaned
},
},
{
name: "date field with options should clean options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeDate,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: nil, // Options should be cleaned
},
},
{
name: "user field with options should clean options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeUser,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: nil, // Options should be cleaned
},
},
// Test options preservation for types that support options
{
name: "select field with options should preserve options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeSelect,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{Name: "Option 1", Color: "#123456"},
},
},
},
{
name: "multiselect field with options should preserve options",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeMultiselect,
},
Attrs: CPAAttrs{
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{Name: "Option 1", Color: "#123456"},
},
},
},
// Test syncing attributes cleaning for types that don't support syncing
{
name: "select field with LDAP and SAML should clean syncing attributes",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeSelect,
},
Attrs: CPAAttrs{
LDAP: "ldap_attribute",
SAML: "saml_attribute",
Options: []*CustomProfileAttributesSelectOption{
{
ID: NewId(),
Name: "Option 1",
Color: "#123456",
},
},
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
LDAP: "", // Should be cleaned
SAML: "", // Should be cleaned
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{Name: "Option 1", Color: "#123456"},
},
},
},
{
name: "date field with LDAP and SAML should clean syncing attributes",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeDate,
},
Attrs: CPAAttrs{
LDAP: "ldap_attribute",
SAML: "saml_attribute",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
LDAP: "", // Should be cleaned
SAML: "", // Should be cleaned
},
},
// Test syncing attributes preservation for types that support syncing
{
name: "text field with LDAP and SAML should preserve syncing attributes",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
LDAP: "ldap_attribute",
SAML: "saml_attribute",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
LDAP: "ldap_attribute", // Should be preserved
SAML: "saml_attribute", // Should be preserved
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.field.SanitizeAndValidate()
if tt.expectError {
require.NotNil(t, err)
require.Equal(t, tt.errorId, err.Id)
} else {
var ogErr error
if err != nil {
ogErr = err.Unwrap()
}
require.Nilf(t, err, "unexpected error: %v, with original error: %v", err, ogErr)
assert.Equal(t, tt.expectedAttrs.Visibility, tt.field.Attrs.Visibility)
assert.Equal(t, tt.expectedAttrs.ValueType, tt.field.Attrs.ValueType)
for i := range tt.expectedAttrs.Options {
if tt.checkOptionsID {
assert.Equal(t, tt.expectedAttrs.Options[i].ID, tt.field.Attrs.Options[i].ID)
}
assert.Equal(t, tt.expectedAttrs.Options[i].Name, tt.field.Attrs.Options[i].Name)
assert.Equal(t, tt.expectedAttrs.Options[i].Color, tt.field.Attrs.Options[i].Color)
}
}
})
}
// Test managed fields functionality
t.Run("managed fields", func(t *testing.T) {
managedTests := []struct {
name string
field *CPAField
expectError bool
errorId string
expectedAttrs CPAAttrs
}{
{
name: "valid managed field with admin value",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Managed: "admin",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Managed: "admin",
},
},
{
name: "managed field with whitespace should be trimmed",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Managed: " admin ",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Managed: "admin",
},
},
{
name: "field with empty managed should be allowed",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Managed: "",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Managed: "",
},
},
{
name: "field with invalid managed value should fail",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Managed: "invalid",
},
},
expectError: true,
errorId: "app.custom_profile_attributes.sanitize_and_validate.app_error",
},
{
name: "managed field should clear LDAP sync properties",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText,
},
Attrs: CPAAttrs{
Managed: "admin",
LDAP: "ldap_attribute",
SAML: "saml_attribute",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Managed: "admin",
LDAP: "", // Should be cleared
SAML: "", // Should be cleared
},
},
{
name: "managed field should clear sync properties even when field supports syncing",
field: &CPAField{
PropertyField: PropertyField{
Type: PropertyFieldTypeText, // Text fields support syncing
},
Attrs: CPAAttrs{
Managed: "admin",
LDAP: "ldap_attribute",
},
},
expectError: false,
expectedAttrs: CPAAttrs{
Visibility: CustomProfileAttributesVisibilityDefault,
Managed: "admin",
LDAP: "", // Should be cleared due to mutual exclusivity
SAML: "",
},
},
}
for _, tt := range managedTests {
t.Run(tt.name, func(t *testing.T) {
err := tt.field.SanitizeAndValidate()
if tt.expectError {
require.NotNil(t, err)
require.Equal(t, tt.errorId, err.Id)
} else {
require.Nil(t, err)
assert.Equal(t, tt.expectedAttrs.Visibility, tt.field.Attrs.Visibility)
assert.Equal(t, tt.expectedAttrs.Managed, tt.field.Attrs.Managed)
assert.Equal(t, tt.expectedAttrs.LDAP, tt.field.Attrs.LDAP)
assert.Equal(t, tt.expectedAttrs.SAML, tt.field.Attrs.SAML)
}
})
}
})
}
func TestSanitizeAndValidatePropertyValue(t *testing.T) {
t.Run("text field type", func(t *testing.T) {
t.Run("valid text", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeText}}, json.RawMessage(`"hello world"`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Equal(t, "hello world", value)
})
t.Run("empty text should be allowed", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeText}}, json.RawMessage(`""`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Empty(t, value)
})
t.Run("invalid JSON", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeText}}, json.RawMessage(`invalid`))
require.Error(t, err)
})
t.Run("wrong type", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeText}}, json.RawMessage(`123`))
require.Error(t, err)
require.Contains(t, err.Error(), "json: cannot unmarshal number into Go value of type string")
})
t.Run("value too long", func(t *testing.T) {
longValue := strings.Repeat("a", CPAValueTypeTextMaxLength+1)
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeText}}, json.RawMessage(fmt.Sprintf(`"%s"`, longValue)))
require.Error(t, err)
require.Equal(t, "value too long", err.Error())
})
})
t.Run("date field type", func(t *testing.T) {
t.Run("valid date", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeDate}}, json.RawMessage(`"2023-01-01"`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Equal(t, "2023-01-01", value)
})
t.Run("empty date should be allowed", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeDate}}, json.RawMessage(`""`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Empty(t, value)
})
})
t.Run("select field type", func(t *testing.T) {
t.Run("valid option", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{
PropertyField: PropertyField{Type: PropertyFieldTypeSelect},
Attrs: CPAAttrs{
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{ID: "option1"},
},
}}, json.RawMessage(`"option1"`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Equal(t, "option1", value)
})
t.Run("invalid option", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeSelect}}, json.RawMessage(`"option1"`))
require.Error(t, err)
})
t.Run("empty option should be allowed", func(t *testing.T) {
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeSelect}}, json.RawMessage(`""`))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Empty(t, value)
})
})
t.Run("user field type", func(t *testing.T) {
t.Run("valid user ID", func(t *testing.T) {
validID := NewId()
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeUser}}, json.RawMessage(fmt.Sprintf(`"%s"`, validID)))
require.NoError(t, err)
var value string
require.NoError(t, json.Unmarshal(result, &value))
require.Equal(t, validID, value)
})
t.Run("empty user ID should be allowed", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeUser}}, json.RawMessage(`""`))
require.NoError(t, err)
})
t.Run("invalid user ID format", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeUser}}, json.RawMessage(`"invalid-id"`))
require.Error(t, err)
require.Equal(t, "invalid user id", err.Error())
})
})
t.Run("multiselect field type", func(t *testing.T) {
t.Run("valid options", func(t *testing.T) {
option1ID := NewId()
option2ID := NewId()
option3ID := NewId()
result, err := SanitizeAndValidatePropertyValue(&CPAField{
PropertyField: PropertyField{Type: PropertyFieldTypeMultiselect},
Attrs: CPAAttrs{
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{ID: option1ID},
{ID: option2ID},
{ID: option3ID},
},
}}, json.RawMessage(fmt.Sprintf(`["%s", "%s"]`, option1ID, option2ID)))
require.NoError(t, err)
var values []string
require.NoError(t, json.Unmarshal(result, &values))
require.Equal(t, []string{option1ID, option2ID}, values)
})
t.Run("empty array", func(t *testing.T) {
option1ID := NewId()
option2ID := NewId()
option3ID := NewId()
_, err := SanitizeAndValidatePropertyValue(&CPAField{
PropertyField: PropertyField{Type: PropertyFieldTypeMultiselect},
Attrs: CPAAttrs{
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{ID: option1ID},
{ID: option2ID},
{ID: option3ID},
},
}}, json.RawMessage(`[]`))
require.NoError(t, err)
})
t.Run("array with empty values should filter them out", func(t *testing.T) {
option1ID := NewId()
option2ID := NewId()
option3ID := NewId()
result, err := SanitizeAndValidatePropertyValue(&CPAField{
PropertyField: PropertyField{Type: PropertyFieldTypeMultiselect},
Attrs: CPAAttrs{
Options: PropertyOptions[*CustomProfileAttributesSelectOption]{
{ID: option1ID},
{ID: option2ID},
{ID: option3ID},
},
}}, json.RawMessage(fmt.Sprintf(`["%s", "", "%s", " ", "%s"]`, option1ID, option2ID, option3ID)))
require.NoError(t, err)
var values []string
require.NoError(t, json.Unmarshal(result, &values))
require.Equal(t, []string{option1ID, option2ID, option3ID}, values)
})
})
t.Run("multiuser field type", func(t *testing.T) {
t.Run("valid user IDs", func(t *testing.T) {
validID1 := NewId()
validID2 := NewId()
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeMultiuser}}, json.RawMessage(fmt.Sprintf(`["%s", "%s"]`, validID1, validID2)))
require.NoError(t, err)
var values []string
require.NoError(t, json.Unmarshal(result, &values))
require.Equal(t, []string{validID1, validID2}, values)
})
t.Run("empty array", func(t *testing.T) {
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeMultiuser}}, json.RawMessage(`[]`))
require.NoError(t, err)
})
t.Run("array with empty strings should be filtered out", func(t *testing.T) {
validID1 := NewId()
validID2 := NewId()
result, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeMultiuser}}, json.RawMessage(fmt.Sprintf(`["%s", "", " ", "%s"]`, validID1, validID2)))
require.NoError(t, err)
var values []string
require.NoError(t, json.Unmarshal(result, &values))
require.Equal(t, []string{validID1, validID2}, values)
})
t.Run("array with invalid ID should return error", func(t *testing.T) {
validID1 := NewId()
_, err := SanitizeAndValidatePropertyValue(&CPAField{PropertyField: PropertyField{Type: PropertyFieldTypeMultiuser}}, json.RawMessage(fmt.Sprintf(`["%s", "invalid-id"]`, validID1)))
require.Error(t, err)
require.Equal(t, "invalid user id: invalid-id", err.Error())
})
})
}
func TestCPAField_IsAdminManaged(t *testing.T) {
tests := []struct {
name string
field *CPAField
expected bool
}{
{
name: "field with managed admin attribute should return true",
field: &CPAField{
Attrs: CPAAttrs{
Managed: "admin",
},
},
expected: true,
},
{
name: "field with empty managed attribute should return false",
field: &CPAField{
Attrs: CPAAttrs{
Managed: "",
},
},
expected: false,
},
{
name: "field with non-admin managed attribute should return false",
field: &CPAField{
Attrs: CPAAttrs{
Managed: "user",
},
},
expected: false,
},
{
name: "field with no managed attribute should return false",
field: &CPAField{
Attrs: CPAAttrs{},
},
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.field.IsAdminManaged()
assert.Equal(t, tt.expected, result)
})
}
}