package test import ( "RenewCMS/internal/infrastructure/config" "errors" "io/fs" "os" "path/filepath" "strings" "testing" ) func TestParseDotenv(t *testing.T) { cases := map[string]struct { input string want map[string]string }{ "plain assignment": {"A=1", map[string]string{"A": "1"}}, "spaces around the equals sign": {"A = 1", map[string]string{"A": "1"}}, "export prefix": {"export A=1", map[string]string{"A": "1"}}, "empty value": {"A=", map[string]string{"A": ""}}, "blank and comment lines": {"\n# note\n \nA=1\n # indented\n", map[string]string{"A": "1"}}, "inline comment on an unquoted value": {"A=value # trailing", map[string]string{"A": "value"}}, "hash without a leading space": {"A=pass#word", map[string]string{"A": "pass#word"}}, "equals sign inside the value": {"A=b=c", map[string]string{"A": "b=c"}}, "URL value": {"A=http://localhost:5173/x?y=1", map[string]string{"A": "http://localhost:5173/x?y=1"}}, "double quoted keeps spaces and hash": {`A=" a # b "`, map[string]string{"A": " a # b "}}, "double quoted escapes": {`A="l1\nl2\t\"q\" \\ \x"`, map[string]string{"A": "l1\nl2\t\"q\" \\ \\x"}}, "double quoted then comment": {`A="v" # note`, map[string]string{"A": "v"}}, "single quoted is literal": {`A='a\nb # c'`, map[string]string{"A": `a\nb # c`}}, "dollar signs are not expanded": {"A=pa$$word${B}", map[string]string{"A": "pa$$word${B}"}}, "dotted names": {"a.b_c1=2", map[string]string{"a.b_c1": "2"}}, "windows line endings": {"A=1\r\nB=2\r\n", map[string]string{"A": "1", "B": "2"}}, "byte order mark": {"\xef\xbb\xbfA=1", map[string]string{"A": "1"}}, "last duplicate wins": {"A=1\nA=2", map[string]string{"A": "2"}}, "no trailing newline": {"A=1\nB=2", map[string]string{"A": "1", "B": "2"}}, } for name, tc := range cases { t.Run("Parses "+name, func(t *testing.T) { got, err := config.ParseDotenv(strings.NewReader(tc.input)) if err != nil { t.Fatalf("unexpected error %v", err) } if len(got) != len(tc.want) { t.Fatalf("expected %v, got %v", tc.want, got) } for key, value := range tc.want { if got[key] != value { t.Errorf("%s: expected %q, got %q", key, value, got[key]) } } }) } rejected := map[string]string{ "a line with no equals sign": "JUST_A_WORD", "an empty name": "=value", "a name with a space": "MY VAR=1", "a name with a dash": "MY-VAR=1", "an unterminated double quote": `A="open`, "a trailing backslash": `A="open\`, "an unterminated single quote": `A='open`, } for name, input := range rejected { t.Run("Rejects "+name+" and names the line", func(t *testing.T) { _, err := config.ParseDotenv(strings.NewReader("OK=1\n" + input)) if err == nil || !strings.Contains(err.Error(), "line 2") { t.Errorf("expected a line 2 error, got %v", err) } }) } t.Run("Rejects a line longer than the budget", func(t *testing.T) { _, err := config.ParseDotenv(strings.NewReader("A=" + strings.Repeat("x", 2<<20))) if err == nil { t.Error("expected an error") } }) } func TestLoadDotenv(t *testing.T) { write := func(t *testing.T, content string) string { t.Helper() path := filepath.Join(t.TempDir(), ".env") if err := os.WriteFile(path, []byte(content), 0o600); err != nil { t.Fatal(err) } return path } t.Run("Sets variables that are not already set", func(t *testing.T) { t.Setenv("DOTENV_TEST_NEW", "") _ = os.Unsetenv("DOTENV_TEST_NEW") err := config.LoadDotenv(write(t, "DOTENV_TEST_NEW=from-file")) if err != nil || os.Getenv("DOTENV_TEST_NEW") != "from-file" { t.Errorf("expected from-file, got %q (%v)", os.Getenv("DOTENV_TEST_NEW"), err) } _ = os.Unsetenv("DOTENV_TEST_NEW") }) t.Run("Never overrides a variable already in the environment", func(t *testing.T) { t.Setenv("DOTENV_TEST_KEEP", "from-env") _ = config.LoadDotenv(write(t, "DOTENV_TEST_KEEP=from-file")) if os.Getenv("DOTENV_TEST_KEEP") != "from-env" { t.Errorf("environment was overridden: %q", os.Getenv("DOTENV_TEST_KEEP")) } }) t.Run("Keeps an explicitly empty environment variable", func(t *testing.T) { t.Setenv("DOTENV_TEST_EMPTY", "") _ = config.LoadDotenv(write(t, "DOTENV_TEST_EMPTY=from-file")) if value, set := os.LookupEnv("DOTENV_TEST_EMPTY"); !set || value != "" { t.Errorf("expected an empty value, got %q set=%v", value, set) } }) t.Run("Applies nothing when any line is malformed", func(t *testing.T) { _ = os.Unsetenv("DOTENV_TEST_ATOMIC") err := config.LoadDotenv(write(t, "DOTENV_TEST_ATOMIC=1\nbroken line")) if err == nil { t.Fatal("expected an error") } if _, set := os.LookupEnv("DOTENV_TEST_ATOMIC"); set { t.Error("a partially applied file must not leak variables") } }) t.Run("Names the file in a parse error", func(t *testing.T) { path := write(t, "broken line") err := config.LoadDotenv(path) if err == nil || !strings.Contains(err.Error(), path) { t.Errorf("expected the path in the error, got %v", err) } }) t.Run("Reports a missing file as fs.ErrNotExist", func(t *testing.T) { err := config.LoadDotenv(filepath.Join(t.TempDir(), "absent.env")) if !errors.Is(err, fs.ErrNotExist) { t.Errorf("expected fs.ErrNotExist, got %v", err) } }) }