Files
filebrowser/backend/common/settings/generator_test.go
T
256f4ca0b8 Beta/v0.8.4 (#1187)
Co-authored-by: Max <mail@kjmax.com>
Co-authored-by: Kurami <kurami32_kbm@proton.me>
2025-09-05 18:56:18 -05:00

471 lines
14 KiB
Go

package settings
import (
"os"
"regexp"
"strings"
"testing"
)
// Test structs for comprehensive testing
type TestConfig struct {
PublicField string `json:"publicField"` // A regular public field
SecretField string `json:"secretField"` // secret: this should be hidden
DeprecatedField string `json:"deprecatedField"` // deprecated: this field is old
EmptyField string `json:"emptyField"` // An empty string field
DefaultField string `json:"defaultField"` // A field that matches default
NonDefaultField string `json:"nonDefaultField"` // A field that differs from default
NestedStruct TestNested `json:"nestedStruct"`
}
type TestNested struct {
NestedPublic string `json:"nestedPublic"` // A nested public field
NestedSecret string `json:"nestedSecret"` // secret: nested secret field
NestedDeprecated string `json:"nestedDeprecated"` // deprecated: nested deprecated field
}
// Helper to create test source files for comment parsing
func createTestSourceFile(t *testing.T, content string) (string, func()) {
tmpFile, err := os.CreateTemp("", "test_*.go")
if err != nil {
t.Fatal(err)
}
if _, err := tmpFile.WriteString(content); err != nil {
t.Fatal(err)
}
tmpFile.Close()
cleanup := func() {
os.Remove(tmpFile.Name())
}
return tmpFile.Name(), cleanup
}
func TestGenerateConfigYaml_StringQuoting(t *testing.T) {
reNumber := regexp.MustCompile(`^-?\d+(\.\d+)?$`)
// Create a simple test source file for this test
sourceContent := `package settings
type Settings struct {
UserDefaults UserDefaults ` + "`json:\"userDefaults\"`" + `
Auth Auth ` + "`json:\"auth\"`" + `
}
type UserDefaults struct {
Locale string ` + "`json:\"locale\"`" + `
}
type Auth struct {
AdminUsername string ` + "`json:\"adminUsername\"`" + `
}
`
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
defer cleanup()
sourcePath := tmpFile[:strings.LastIndex(tmpFile, "/")]
// Create real Settings with string values to test quoting
settings := &Settings{
UserDefaults: UserDefaults{
Locale: "en-US",
},
Auth: Auth{
AdminUsername: "admin",
},
}
// Test with no filtering - should quote all strings
yamlOutput, err := GenerateConfigYamlWithSource(settings, false, true, false, sourcePath)
if err != nil {
t.Fatalf("GenerateConfigYaml failed: %v", err)
}
// All string values should be quoted
lines := strings.Split(yamlOutput, "\n")
for _, line := range lines {
if strings.Contains(line, ":") && !strings.HasSuffix(strings.TrimSpace(line), ":") {
// This is a key-value line
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
value := strings.TrimSpace(parts[1])
// Remove comments
if commentIdx := strings.Index(value, "#"); commentIdx >= 0 {
value = strings.TrimSpace(value[:commentIdx])
}
// Skip empty values, arrays, objects, booleans
if value == "" || value == "false" || value == "true" || strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") {
continue
}
// Check if it's a number (should NOT be quoted)
if reNumber.MatchString(value) {
if strings.HasPrefix(value, "\"") {
t.Errorf("Numeric value should not be quoted: %s (value: '%s')", line, value)
}
continue
}
// Everything else should be a quoted string
if !strings.HasPrefix(value, "\"") || !strings.HasSuffix(value, "\"") {
t.Errorf("String value should be quoted: %s (value: '%s')", line, value)
}
}
}
}
}
func TestGenerateConfigYaml_SecretHiding(t *testing.T) {
// Create temporary source file with our test structs
sourceContent := `package settings
type TestConfig struct {
PublicField string ` + "`json:\"publicField\"`" + ` // A regular public field
SecretField string ` + "`json:\"secretField\"`" + ` // secret: this should be hidden
}
type TestNested struct {
NestedPublic string ` + "`json:\"nestedPublic\"`" + ` // A nested public field
NestedSecret string ` + "`json:\"nestedSecret\"`" + ` // secret: nested secret field
}
`
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
defer cleanup()
// Test secret detection
_, secrets, _, err := CollectCommentsAndSecrets(tmpFile)
if err != nil {
t.Fatalf("CollectCommentsAndSecrets failed: %v", err)
}
// Verify secret fields are detected
if !secrets["TestConfig"]["SecretField"] {
t.Error("SecretField should be marked as secret")
}
if !secrets["TestNested"]["NestedSecret"] {
t.Error("NestedSecret should be marked as secret")
}
if secrets["TestConfig"]["PublicField"] {
t.Error("PublicField should not be marked as secret")
}
// Test that secrets are properly collected
if len(secrets["TestConfig"]) != 1 {
t.Errorf("Expected 1 secret field in TestConfig, got %d", len(secrets["TestConfig"]))
}
}
func TestGenerateConfigYaml_DeprecatedFiltering(t *testing.T) {
// Create temporary source file with deprecated fields
sourceContent := `package settings
type TestConfig struct {
PublicField string ` + "`json:\"publicField\"`" + ` // A regular public field
DeprecatedField string ` + "`json:\"deprecatedField\"`" + ` // deprecated: this field is old
}
`
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
defer cleanup()
// Test deprecated detection
_, _, deprecated, err := CollectCommentsAndSecrets(tmpFile)
if err != nil {
t.Fatalf("CollectCommentsAndSecrets failed: %v", err)
}
// Verify deprecated fields are detected
if !deprecated["TestConfig"]["DeprecatedField"] {
t.Error("DeprecatedField should be marked as deprecated")
}
if deprecated["TestConfig"]["PublicField"] {
t.Error("PublicField should not be marked as deprecated")
}
}
func TestGenerateConfigYaml_FullVsFiltered(t *testing.T) {
// Create minimal test source
sourceContent := `package settings
type Settings struct {
UserDefaults UserDefaults ` + "`json:\"userDefaults\"`" + `
}
type UserDefaults struct {
DarkMode bool ` + "`json:\"darkMode\"`" + `
Locale string ` + "`json:\"locale\"`" + `
}
`
tmpFile, cleanup := createTestSourceFile(t, sourceContent)
defer cleanup()
sourcePath := tmpFile[:strings.LastIndex(tmpFile, "/")]
// Create a config with both default and non-default values
config := &Settings{
UserDefaults: UserDefaults{
DarkMode: true, // This matches default
Locale: "es", // This differs from default ("en")
},
}
// Test full=true - should show all fields
fullYaml, err := GenerateConfigYamlWithSource(config, false, true, false, sourcePath)
if err != nil {
t.Fatalf("GenerateConfigYaml with full=true failed: %v", err)
}
// Test full=false - should only show non-default fields
filteredYaml, err := GenerateConfigYamlWithSource(config, false, false, false, sourcePath)
if err != nil {
t.Fatalf("GenerateConfigYaml with full=false failed: %v", err)
}
// Full output should be longer than filtered
if len(fullYaml) <= len(filteredYaml) {
t.Error("Full YAML should be longer than filtered YAML")
}
// Filtered output should contain the non-default field
if !strings.Contains(filteredYaml, "locale") {
t.Error("Filtered YAML should contain non-default locale field")
}
// Full output should contain default fields
if !strings.Contains(fullYaml, "darkMode") {
t.Error("Full YAML should contain darkMode field")
}
}
func TestGenerateConfigYaml_CommentsOnOff(t *testing.T) {
// Use the actual settings source directory since comments work there
config := &Settings{
UserDefaults: UserDefaults{
Locale: "en",
},
}
// Test with comments=true using the real source
withComments, err := GenerateConfigYaml(config, true, true, false)
if err != nil {
t.Fatalf("GenerateConfigYaml with comments=true failed: %v", err)
}
// Test with comments=false using the real source
withoutComments, err := GenerateConfigYaml(config, false, true, false)
if err != nil {
t.Fatalf("GenerateConfigYaml with comments=false failed: %v", err)
}
// Output with comments should be longer
if len(withComments) <= len(withoutComments) {
t.Errorf("YAML with comments (%d chars) should be longer than without comments (%d chars)", len(withComments), len(withoutComments))
}
// With comments should contain '#' characters
if !strings.Contains(withComments, "#") {
t.Error("YAML with comments should contain '#' characters")
}
// Without comments should not contain '#' characters
if strings.Contains(withoutComments, "#") {
t.Error("YAML without comments should not contain '#' characters")
}
}
func TestGenerateConfigYaml_IntegrationTest(t *testing.T) {
reNumber := regexp.MustCompile(`^-?\d+(\.\d+)?$`)
// Comprehensive test with all features
config := &Settings{
UserDefaults: UserDefaults{
Locale: "es", // Non-default
DarkMode: true, // Default
DisableOfficePreviewExt: ".docx .xlsx", // This is deprecated
},
Auth: Auth{
Key: "secret123", // This is secret
AdminUsername: "admin", // This is secret
AdminPassword: "password", // This is secret
},
}
tests := []struct {
name string
showComments bool
showFull bool
filterDeprecated bool
expectSecret bool
expectDeprecated bool
expectFull bool
expectComments bool
}{
{
name: "API mode: show all including deprecated",
showComments: false,
showFull: true,
filterDeprecated: false,
expectSecret: true, // secrets should be hidden
expectDeprecated: true, // deprecated should be shown
expectFull: true, // all fields shown
expectComments: false, // no comments
},
{
name: "Static config mode: filter deprecated",
showComments: true,
showFull: true,
filterDeprecated: true,
expectSecret: true, // secrets should be hidden
expectDeprecated: false, // deprecated should be filtered
expectFull: true, // all non-deprecated fields shown
expectComments: true, // comments included
},
{
name: "Filtered output: only changes",
showComments: false,
showFull: false,
filterDeprecated: false,
expectSecret: true, // secrets should be hidden
expectDeprecated: false, // might not appear if matches default
expectFull: false, // only non-default fields
expectComments: false, // no comments
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
yamlOutput, err := GenerateConfigYaml(config, tt.showComments, tt.showFull, tt.filterDeprecated)
if err != nil {
t.Fatalf("GenerateConfigYaml failed: %v", err)
}
// Check secret hiding
if tt.expectSecret {
if !strings.Contains(yamlOutput, "**hidden**") {
t.Error("Expected secrets to be hidden with **hidden**")
}
if strings.Contains(yamlOutput, "secret123") {
t.Error("Secret values should not appear in output")
}
}
// Check deprecated field filtering
hasDeprecated := strings.Contains(yamlOutput, "disableOfficePreviewExt")
if tt.expectDeprecated && !hasDeprecated {
t.Error("Expected deprecated field to be present")
}
if !tt.expectDeprecated && hasDeprecated && tt.filterDeprecated {
t.Error("Expected deprecated field to be filtered out")
}
// Check comments
hasComments := strings.Contains(yamlOutput, "#")
if tt.expectComments && !hasComments {
t.Error("Expected comments to be present")
}
if !tt.expectComments && hasComments {
t.Error("Expected no comments")
}
// Check string quoting - all string values should be quoted
lines := strings.Split(yamlOutput, "\n")
for _, line := range lines {
if strings.Contains(line, ":") && !strings.HasSuffix(strings.TrimSpace(line), ":") {
// This is a key-value line
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
value := strings.TrimSpace(parts[1])
// Skip comments and empty values
if commentIdx := strings.Index(value, "#"); commentIdx >= 0 {
// If comment starts at the beginning, there's no value, just a comment
if commentIdx == 0 {
continue
}
value = strings.TrimSpace(value[:commentIdx])
}
// Check if it's a number (should NOT be quoted)
if reNumber.MatchString(value) {
if strings.HasPrefix(value, "\"") {
t.Errorf("Numeric value should not be quoted in line: %s (value: '%s')", line, value)
}
continue
}
// Check if it's a boolean (should NOT be quoted)
if value == "true" || value == "false" {
if strings.HasPrefix(value, "\"") {
t.Errorf("Boolean value should not be quoted in line: %s (value: '%s')", line, value)
}
continue
}
// Skip arrays, objects, and empty values
if value == "" || strings.HasPrefix(value, "[") || strings.HasPrefix(value, "{") || strings.Contains(value, ":") {
continue
}
// Everything else should be a quoted string
if !strings.HasPrefix(value, "\"") || !strings.HasSuffix(value, "\"") {
t.Errorf("String value should be quoted in line: %s (value: '%s')", line, value)
}
}
}
}
t.Logf("Generated YAML for %s:\n%s", tt.name, yamlOutput)
})
}
}
func TestGenerateConfigYaml_EdgeCases(t *testing.T) {
tests := []struct {
name string
config *Settings
desc string
}{
{
name: "empty_config",
config: &Settings{},
desc: "Empty config should generate valid YAML",
},
{
name: "only_defaults",
config: &Settings{
UserDefaults: UserDefaults{
DarkMode: true, // default value
Locale: "en", // default value
},
},
desc: "Config with only default values",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test all combinations
for _, showComments := range []bool{false, true} {
for _, showFull := range []bool{false, true} {
for _, filterDeprecated := range []bool{false, true} {
yamlOutput, err := GenerateConfigYaml(tt.config, showComments, showFull, filterDeprecated)
if err != nil {
t.Fatalf("GenerateConfigYaml failed for %s (comments=%v, full=%v, filterDeprecated=%v): %v",
tt.desc, showComments, showFull, filterDeprecated, err)
}
// Basic validation - should produce valid YAML structure
if yamlOutput == "" {
t.Errorf("Generated empty YAML for %s", tt.desc)
}
// Should not contain obvious error messages (but allow "error" in log levels etc)
if strings.Contains(yamlOutput, "Error:") || strings.Contains(yamlOutput, "ERROR:") {
t.Errorf("YAML contains error for %s", tt.desc)
}
}
}
}
})
}
}