2023-06-19 14:42:47 -07:00
|
|
|
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
2019-06-01 18:18:09 -07:00
|
|
|
|
2023-08-10 19:46:45 -07:00
|
|
|
package config // import "miniflux.app/v2/internal/config"
|
2019-06-01 18:18:09 -07:00
|
|
|
|
|
|
|
import (
|
2025-09-14 10:51:04 -07:00
|
|
|
"net/url"
|
|
|
|
"os"
|
2025-06-12 23:25:15 +02:00
|
|
|
"reflect"
|
2019-06-01 18:18:09 -07:00
|
|
|
"testing"
|
2025-09-14 10:51:04 -07:00
|
|
|
"time"
|
2019-06-01 18:18:09 -07:00
|
|
|
)
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestParseStringValue(t *testing.T) {
|
|
|
|
// Test with non-empty value
|
|
|
|
result := parseStringValue("test", "fallback")
|
|
|
|
if result != "test" {
|
|
|
|
t.Errorf("Expected 'test', got '%s'", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with empty value
|
|
|
|
result = parseStringValue("", "fallback")
|
|
|
|
if result != "fallback" {
|
|
|
|
t.Errorf("Expected 'fallback', got '%s'", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with empty value and empty fallback
|
|
|
|
result = parseStringValue("", "")
|
|
|
|
if result != "" {
|
|
|
|
t.Errorf("Expected empty string, got '%s'", result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-02 18:20:59 -07:00
|
|
|
func TestParseBoolValue(t *testing.T) {
|
2025-09-14 10:51:04 -07:00
|
|
|
// Test with empty value - should return fallback
|
|
|
|
result, err := parseBoolValue("", true)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if result != true {
|
|
|
|
t.Errorf("Expected true, got %v", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test true values
|
|
|
|
trueValues := []string{"1", "yes", "true", "on", "YES", "TRUE", "ON"}
|
|
|
|
for _, value := range trueValues {
|
|
|
|
result, err := parseBoolValue(value, false)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error for value '%s': %v", value, err)
|
|
|
|
}
|
|
|
|
if result != true {
|
|
|
|
t.Errorf("Expected true for '%s', got %v", value, result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test false values
|
|
|
|
falseValues := []string{"0", "no", "false", "off", "NO", "FALSE", "OFF"}
|
|
|
|
for _, value := range falseValues {
|
|
|
|
result, err := parseBoolValue(value, true)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error for value '%s': %v", value, err)
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
2025-09-14 10:51:04 -07:00
|
|
|
if result != false {
|
|
|
|
t.Errorf("Expected false for '%s', got %v", value, result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test invalid value - should return error
|
|
|
|
_, err = parseBoolValue("invalid", false)
|
|
|
|
if err == nil {
|
|
|
|
t.Error("Expected error for invalid boolean value")
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestParseIntValue(t *testing.T) {
|
|
|
|
// Test with empty value - should return fallback
|
|
|
|
result := parseIntValue("", 42)
|
|
|
|
if result != 42 {
|
|
|
|
t.Errorf("Expected 42, got %d", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with valid integer
|
|
|
|
result = parseIntValue("123", 42)
|
|
|
|
if result != 123 {
|
|
|
|
t.Errorf("Expected 123, got %d", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with invalid integer - should return fallback
|
|
|
|
result = parseIntValue("invalid", 42)
|
|
|
|
if result != 42 {
|
|
|
|
t.Errorf("Expected 42, got %d", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with zero
|
|
|
|
result = parseIntValue("0", 42)
|
|
|
|
if result != 0 {
|
|
|
|
t.Errorf("Expected 0, got %d", result)
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestParsedInt64Value(t *testing.T) {
|
|
|
|
// Test with empty value - should return fallback
|
|
|
|
result := ParsedInt64Value("", 42)
|
|
|
|
if result != 42 {
|
|
|
|
t.Errorf("Expected 42, got %d", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with valid int64
|
|
|
|
result = ParsedInt64Value("9223372036854775807", 42)
|
|
|
|
if result != 9223372036854775807 {
|
|
|
|
t.Errorf("Expected 9223372036854775807, got %d", result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with invalid int64 - should return fallback
|
|
|
|
result = ParsedInt64Value("invalid", 42)
|
|
|
|
if result != 42 {
|
|
|
|
t.Errorf("Expected 42, got %d", result)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseStringListValue(t *testing.T) {
|
|
|
|
// Test with empty value - should return fallback
|
|
|
|
fallback := []string{"a", "b"}
|
|
|
|
result := parseStringListValue("", fallback)
|
|
|
|
if !reflect.DeepEqual(result, fallback) {
|
|
|
|
t.Errorf("Expected %v, got %v", fallback, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with single value
|
|
|
|
result = parseStringListValue("item1", nil)
|
|
|
|
expected := []string{"item1"}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with multiple values
|
|
|
|
result = parseStringListValue("item1,item2,item3", nil)
|
|
|
|
expected = []string{"item1", "item2", "item3"}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with duplicates - should remove duplicates
|
|
|
|
result = parseStringListValue("item1,item2,item1", nil)
|
|
|
|
expected = []string{"item1", "item2"}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with spaces
|
|
|
|
result = parseStringListValue(" item1 , item2 , item3 ", nil)
|
|
|
|
expected = []string{"item1", "item2", "item3"}
|
|
|
|
if !reflect.DeepEqual(result, expected) {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestParseDurationValue(t *testing.T) {
|
|
|
|
// Test with empty value - should return fallback
|
|
|
|
fallback := 5 * time.Second
|
|
|
|
result := parseDurationValue("", time.Second, fallback)
|
|
|
|
if result != fallback {
|
|
|
|
t.Errorf("Expected %v, got %v", fallback, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with valid duration
|
|
|
|
result = parseDurationValue("30", time.Second, fallback)
|
|
|
|
expected := 30 * time.Second
|
|
|
|
if result != expected {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with minutes
|
|
|
|
result = parseDurationValue("5", time.Minute, fallback)
|
|
|
|
expected = 5 * time.Minute
|
|
|
|
if result != expected {
|
|
|
|
t.Errorf("Expected %v, got %v", expected, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with invalid value - should return fallback
|
|
|
|
result = parseDurationValue("invalid", time.Second, fallback)
|
|
|
|
if result != fallback {
|
|
|
|
t.Errorf("Expected %v, got %v", fallback, result)
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestParseURLValue(t *testing.T) {
|
|
|
|
// Test with empty value - should return fallback
|
|
|
|
fallbackURL, _ := url.Parse("https://fallback.com")
|
|
|
|
result, err := parseURLValue("", fallbackURL)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if result != fallbackURL {
|
|
|
|
t.Errorf("Expected %v, got %v", fallbackURL, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with valid URL
|
|
|
|
result, err = parseURLValue("https://example.com", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if result.String() != "https://example.com" {
|
|
|
|
t.Errorf("Expected https://example.com, got %s", result.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test with invalid URL - should return fallback and error
|
|
|
|
result, err = parseURLValue("://invalid", fallbackURL)
|
|
|
|
if err == nil {
|
|
|
|
t.Error("Expected error for invalid URL")
|
|
|
|
}
|
|
|
|
if result != fallbackURL {
|
|
|
|
t.Errorf("Expected fallback URL, got %v", result)
|
2019-06-01 18:18:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-09-14 10:51:04 -07:00
|
|
|
func TestConfigFileParsing(t *testing.T) {
|
|
|
|
fileContent := `
|
|
|
|
# This is a comment
|
|
|
|
LOG_FILE=miniflux.log
|
|
|
|
LOG_DATE_TIME=1
|
|
|
|
LOG_FORMAT=json
|
|
|
|
LISTEN_ADDR=:8080,:8443
|
|
|
|
`
|
|
|
|
|
|
|
|
// Write a temporary config file and parse it
|
|
|
|
tmpFile, err := os.CreateTemp("", "miniflux-*.txt")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create temporary file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
defer tmpFile.Close()
|
|
|
|
|
|
|
|
filename := tmpFile.Name()
|
|
|
|
if _, err := tmpFile.WriteString(fileContent); err != nil {
|
|
|
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseFile(filename)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogFile() != "miniflux.log" {
|
|
|
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogDateTime() != true {
|
|
|
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogFormat() != "json" {
|
|
|
|
t.Fatalf("Unexpected log format, got %q", configOptions.LogFormat())
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogLevel() != "info" {
|
|
|
|
t.Fatalf("Unexpected log level, got %q", configOptions.LogLevel())
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(configOptions.ListenAddr()) != 2 || configOptions.ListenAddr()[0] != ":8080" || configOptions.ListenAddr()[1] != ":8443" {
|
|
|
|
t.Fatalf("Unexpected listen addresses, got %v", configOptions.ListenAddr())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestConfigFileParsingWithIncorrectKeyValuePair(t *testing.T) {
|
|
|
|
fileContent := `
|
|
|
|
LOG_FILE=miniflux.log
|
|
|
|
INVALID_LINE
|
|
|
|
`
|
|
|
|
|
|
|
|
// Write a temporary config file and parse it
|
|
|
|
tmpFile, err := os.CreateTemp("", "miniflux-*.txt")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create temporary file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
defer tmpFile.Close()
|
|
|
|
|
|
|
|
filename := tmpFile.Name()
|
|
|
|
if _, err := tmpFile.WriteString(fileContent); err != nil {
|
|
|
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
_, err = configParser.ParseFile(filename)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("Invalid lines should be ignored, but got error:", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseAdminPasswordFileOption(t *testing.T) {
|
|
|
|
tmpFile, err := os.CreateTemp("", "password-*.txt")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create temporary file: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
defer tmpFile.Close()
|
|
|
|
|
|
|
|
password := "supersecret"
|
|
|
|
if _, err := tmpFile.WriteString(password); err != nil {
|
|
|
|
t.Fatalf("Failed to write to temporary file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("ADMIN_PASSWORD_FILE", tmpFile.Name())
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.AdminPassword() != password {
|
|
|
|
t.Fatalf("Unexpected admin password, got %q", configOptions.AdminPassword())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseAdminPasswordFileOptionWithEmptyFile(t *testing.T) {
|
|
|
|
tmpFile, err := os.CreateTemp("", "empty-password-*.txt")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Failed to create temporary file: %v", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
defer tmpFile.Close()
|
|
|
|
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("ADMIN_PASSWORD_FILE", tmpFile.Name())
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
_, err = configParser.ParseEnvironmentVariables()
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("Expected error due to empty password file, but got none")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogFileOptionDefaultValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogFile() != "stderr" {
|
|
|
|
t.Fatalf("Unexpected default log file, got %q", configOptions.LogFile())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogFileOptionWithCustomFilename(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("LOG_FILE", "miniflux.log")
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogFile() != "miniflux.log" {
|
|
|
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogFileOptionWithEmptyValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("LOG_FILE", "")
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogFile() != "stderr" {
|
|
|
|
t.Fatalf("Unexpected log file, got %q", configOptions.LogFile())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogDateTimeOptionDefaultValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogDateTime() != false {
|
|
|
|
t.Fatalf("Unexpected default log datetime, got %v", configOptions.LogDateTime())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogDateTimeOptionWithCustomValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("LOG_DATE_TIME", "true")
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogDateTime() != true {
|
|
|
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogDateTimeOptionWithEmptyValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("LOG_DATE_TIME", "")
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
configOptions, err := configParser.ParseEnvironmentVariables()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpected parsing error: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if configOptions.LogDateTime() != false {
|
|
|
|
t.Fatalf("Unexpected log datetime, got %v", configOptions.LogDateTime())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseLogDateTimeOptionWithIncorrectValue(t *testing.T) {
|
|
|
|
os.Clearenv()
|
|
|
|
os.Setenv("LOG_DATE_TIME", "invalid")
|
|
|
|
|
|
|
|
configParser := NewConfigParser()
|
|
|
|
if _, err := configParser.ParseEnvironmentVariables(); err == nil {
|
|
|
|
t.Fatal("Expected parsing error, got nil")
|
2025-06-12 23:25:15 +02:00
|
|
|
}
|
|
|
|
}
|