package godotenv import ( "os" "testing" ) func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) { key, value, _ := parseLine(rawEnvLine) if key != expectedKey || value != expectedValue { t.Errorf("Expected '%v' to parse as '%v' => '%v', got '%v' => '%v' instead", rawEnvLine, expectedKey, expectedValue, key, value) } } func loadEnvAndCompareValues(t *testing.T, envFileName string, expectedValues map[string]string) { // first up, clear the env os.Clearenv() err := Load(envFileName) if err != nil { t.Fatalf("Error loading %v", envFileName) } for k := range expectedValues { envValue := os.Getenv(k) v := expectedValues[k] if envValue != v { t.Errorf("Mismatch for key '%v': expected '%v' got '%v'", k, v, envValue) } } } func TestLoadWithNoArgsLoadsDotEnv(t *testing.T) { err := Load() if err.Error() != "open .env: no such file or directory" { t.Errorf("Didn't try and open .env by default") } } func TestLoadFileNotFound(t *testing.T) { err := Load("somefilethatwillneverexistever.env") if err == nil { t.Error("File wasn't found but Load didn't return an error") } } func TestLoadPlainEnv(t *testing.T) { envFileName := "fixtures/plain.env" expectedValues := map[string]string{ "OPTION_A": "1", "OPTION_B": "2", "OPTION_C": "3", "OPTION_D": "4", "OPTION_E": "5", } loadEnvAndCompareValues(t, envFileName, expectedValues) } func TestLoadExportedEnv(t *testing.T) { envFileName := "fixtures/exported.env" expectedValues := map[string]string{ "OPTION_A": "2", "OPTION_B": "\n", } loadEnvAndCompareValues(t, envFileName, expectedValues) } func TestLoadQuotedEnv(t *testing.T) { envFileName := "fixtures/quoted.env" expectedValues := map[string]string{ "OPTION_A": "1", "OPTION_B": "2", "OPTION_C": "", "OPTION_D": "\n", "OPTION_E": "1", "OPTION_F": "2", "OPTION_G": "", "OPTION_H": "\n", } loadEnvAndCompareValues(t, envFileName, expectedValues) } func TestParsing(t *testing.T) { // unquoted values parseAndCompare(t, "FOO=bar", "FOO", "bar") // parses values with spaces around equal sign parseAndCompare(t, "FOO =bar", "FOO", "bar") parseAndCompare(t, "FOO= bar", "FOO", "bar") // parses double quoted values parseAndCompare(t, "FOO=\"bar\"", "FOO", "bar") // parses single quoted values parseAndCompare(t, "FOO='bar'", "FOO", "bar") // parses escaped double quotes parseAndCompare(t, "FOO=escaped\\\"bar\"", "FOO", "escaped\"bar") // parses yaml style options parseAndCompare(t, "OPTION_A: 1", "OPTION_A", "1") // parses export keyword parseAndCompare(t, "export OPTION_A=2", "OPTION_A", "2") parseAndCompare(t, "export OPTION_B='\\n'", "OPTION_B", "\n") // it 'expands newlines in quoted strings' do // expect(env('FOO="bar\nbaz"')).to eql('FOO' => "bar\nbaz") parseAndCompare(t, "FOO=\"bar\\nbaz\"", "FOO", "bar\nbaz") // it 'parses varibales with "." in the name' do // expect(env('FOO.BAR=foobar')).to eql('FOO.BAR' => 'foobar') parseAndCompare(t, "FOO.BAR=foobar", "FOO.BAR", "foobar") // it 'strips unquoted values' do // expect(env('foo=bar ')).to eql('foo' => 'bar') # not 'bar ' parseAndCompare(t, "FOO=bar ", "FOO", "bar") // it 'ignores inline comments' do // expect(env("foo=bar # this is foo")).to eql('foo' => 'bar') parseAndCompare(t, "FOO=bar # this is foo", "FOO", "bar") // it 'allows # in quoted value' do // expect(env('foo="bar#baz" # comment')).to eql('foo' => 'bar#baz') parseAndCompare(t, "FOO=\"bar#baz\" # comment", "FOO", "bar#baz") parseAndCompare(t, "FOO='bar#baz' # comment", "FOO", "bar#baz") parseAndCompare(t, "FOO=\"bar#baz#bang\" # comment", "FOO", "bar#baz#bang") // it 'parses # in quoted values' do // expect(env('foo="ba#r"')).to eql('foo' => 'ba#r') // expect(env("foo='ba#r'")).to eql('foo' => 'ba#r') parseAndCompare(t, "FOO=\"ba#r\"", "FOO", "ba#r") parseAndCompare(t, "FOO='ba#r'", "FOO", "ba#r") // it 'throws an error if line format is incorrect' do // expect{env('lol$wut')}.to raise_error(Dotenv::FormatError) badlyFormattedLine := "lol$wut" _, _, err := parseLine(badlyFormattedLine) if err == nil { t.Errorf("Expected \"%v\" to return error, but it didn't", badlyFormattedLine) } } func TestLinesToIgnore(t *testing.T) { // it 'ignores empty lines' do // expect(env("\n \t \nfoo=bar\n \nfizz=buzz")).to eql('foo' => 'bar', 'fizz' => 'buzz') if !isIgnoredLine("\n") { t.Error("Line with nothing but line break wasn't ignored") } if !isIgnoredLine("\t\t ") { t.Error("Line full of whitespace wasn't ignored") } // it 'ignores comment lines' do // expect(env("\n\n\n # HERE GOES FOO \nfoo=bar")).to eql('foo' => 'bar') if !isIgnoredLine("# comment") { t.Error("Comment wasn't ignored") } if !isIgnoredLine("\t#comment") { t.Error("Indented comment wasn't ignored") } // make sure we're not getting false positives if isIgnoredLine("export OPTION_B='\\n'") { t.Error("ignoring a perfectly valid line to parse") } }