Files
RenewCMS/test/dotenv_test.go
T

158 lines
5.4 KiB
Go

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)
}
})
}