123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- package gotenv_test
- import (
- "bufio"
- "os"
- "strings"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/subosito/gotenv"
- )
- var formats = []struct {
- in string
- out gotenv.Env
- preset bool
- }{
- // parses unquoted values
- {`FOO=bar`, gotenv.Env{"FOO": "bar"}, false},
- // parses values with spaces around equal sign
- {`FOO =bar`, gotenv.Env{"FOO": "bar"}, false},
- {`FOO= bar`, gotenv.Env{"FOO": "bar"}, false},
- // parses values with leading spaces
- {` FOO=bar`, gotenv.Env{"FOO": "bar"}, false},
- // parses values with following spaces
- {`FOO=bar `, gotenv.Env{"FOO": "bar"}, false},
- // parses double quoted values
- {`FOO="bar"`, gotenv.Env{"FOO": "bar"}, false},
- // parses double quoted values with following spaces
- {`FOO="bar" `, gotenv.Env{"FOO": "bar"}, false},
- // parses single quoted values
- {`FOO='bar'`, gotenv.Env{"FOO": "bar"}, false},
- // parses single quoted values with following spaces
- {`FOO='bar' `, gotenv.Env{"FOO": "bar"}, false},
- // parses escaped double quotes
- {`FOO="escaped\"bar"`, gotenv.Env{"FOO": `escaped"bar`}, false},
- // parses empty values
- {`FOO=`, gotenv.Env{"FOO": ""}, false},
- // expands variables found in values
- {"FOO=test\nBAR=$FOO", gotenv.Env{"FOO": "test", "BAR": "test"}, false},
- // parses variables wrapped in brackets
- {"FOO=test\nBAR=${FOO}bar", gotenv.Env{"FOO": "test", "BAR": "testbar"}, false},
- // reads variables from ENV when expanding if not found in local env
- {`BAR=$FOO`, gotenv.Env{"BAR": "test"}, true},
- // expands undefined variables to an empty string
- {`BAR=$FOO`, gotenv.Env{"BAR": ""}, false},
- // expands variables in quoted strings
- {"FOO=test\nBAR=\"quote $FOO\"", gotenv.Env{"FOO": "test", "BAR": "quote test"}, false},
- // does not expand variables in single quoted strings
- {"BAR='quote $FOO'", gotenv.Env{"BAR": "quote $FOO"}, false},
- // does not expand escaped variables
- {`FOO="foo\$BAR"`, gotenv.Env{"FOO": "foo$BAR"}, false},
- {`FOO="foo\${BAR}"`, gotenv.Env{"FOO": "foo${BAR}"}, false},
- {"FOO=test\nBAR=\"foo\\${FOO} ${FOO}\"", gotenv.Env{"FOO": "test", "BAR": "foo${FOO} test"}, false},
- // parses yaml style options
- {"OPTION_A: 1", gotenv.Env{"OPTION_A": "1"}, false},
- // parses export keyword
- {"export OPTION_A=2", gotenv.Env{"OPTION_A": "2"}, false},
- // allows export line if you want to do it that way
- {"OPTION_A=2\nexport OPTION_A", gotenv.Env{"OPTION_A": "2"}, false},
- // expands newlines in quoted strings
- {`FOO="bar\nbaz"`, gotenv.Env{"FOO": "bar\nbaz"}, false},
- // parses variables with "." in the name
- {`FOO.BAR=foobar`, gotenv.Env{"FOO.BAR": "foobar"}, false},
- // strips unquoted values
- {`foo=bar `, gotenv.Env{"foo": "bar"}, false}, // not 'bar '
- // ignores empty lines
- {"\n \t \nfoo=bar\n \nfizz=buzz", gotenv.Env{"foo": "bar", "fizz": "buzz"}, false},
- // ignores inline comments
- {"foo=bar # this is foo", gotenv.Env{"foo": "bar"}, false},
- // allows # in quoted value
- {`foo="bar#baz" # comment`, gotenv.Env{"foo": "bar#baz"}, false},
- // ignores comment lines
- {"\n\n\n # HERE GOES FOO \nfoo=bar", gotenv.Env{"foo": "bar"}, false},
- // parses # in quoted values
- {`foo="ba#r"`, gotenv.Env{"foo": "ba#r"}, false},
- {"foo='ba#r'", gotenv.Env{"foo": "ba#r"}, false},
- // parses # in quoted values with following spaces
- {`foo="ba#r" `, gotenv.Env{"foo": "ba#r"}, false},
- {`foo='ba#r' `, gotenv.Env{"foo": "ba#r"}, false},
- // supports carriage return
- {"FOO=bar\rbaz=fbb", gotenv.Env{"FOO": "bar", "baz": "fbb"}, false},
- // supports carriage return combine with new line
- {"FOO=bar\r\nbaz=fbb", gotenv.Env{"FOO": "bar", "baz": "fbb"}, false},
- // expands carriage return in quoted strings
- {`FOO="bar\rbaz"`, gotenv.Env{"FOO": "bar\rbaz"}, false},
- // escape $ properly when no alphabets/numbers/_ are followed by it
- {`FOO="bar\\$ \\$\\$"`, gotenv.Env{"FOO": "bar$ $$"}, false},
- // ignore $ when it is not escaped and no variable is followed by it
- {`FOO="bar $ "`, gotenv.Env{"FOO": "bar $ "}, false},
- // parses unquoted values with spaces after separator
- {`FOO= bar`, gotenv.Env{"FOO": "bar"}, false},
- // allows # in quoted value with spaces after separator
- {`foo= "bar#baz" # comment`, gotenv.Env{"foo": "bar#baz"}, false},
- }
- var errorFormats = []struct {
- in string
- out gotenv.Env
- err string
- }{
- // allows export line if you want to do it that way and checks for unset variables
- {"OPTION_A=2\nexport OH_NO_NOT_SET", gotenv.Env{"OPTION_A": "2"}, "line `export OH_NO_NOT_SET` has an unset variable"},
- // throws an error if line format is incorrect
- {`lol$wut`, gotenv.Env{}, "line `lol$wut` doesn't match format"},
- }
- var fixtures = []struct {
- filename string
- results gotenv.Env
- }{
- {
- "fixtures/exported.env",
- gotenv.Env{
- "OPTION_A": "2",
- "OPTION_B": `\n`,
- },
- },
- {
- "fixtures/plain.env",
- gotenv.Env{
- "OPTION_A": "1",
- "OPTION_B": "2",
- "OPTION_C": "3",
- "OPTION_D": "4",
- "OPTION_E": "5",
- },
- },
- {
- "fixtures/quoted.env",
- gotenv.Env{
- "OPTION_A": "1",
- "OPTION_B": "2",
- "OPTION_C": "",
- "OPTION_D": `\n`,
- "OPTION_E": "1",
- "OPTION_F": "2",
- "OPTION_G": "",
- "OPTION_H": "\n",
- },
- },
- {
- "fixtures/yaml.env",
- gotenv.Env{
- "OPTION_A": "1",
- "OPTION_B": "2",
- "OPTION_C": "",
- "OPTION_D": `\n`,
- },
- },
- }
- func TestParse(t *testing.T) {
- for _, tt := range formats {
- if tt.preset {
- os.Setenv("FOO", "test")
- }
- exp := gotenv.Parse(strings.NewReader(tt.in))
- assert.Equal(t, tt.out, exp)
- os.Clearenv()
- }
- }
- func TestStrictParse(t *testing.T) {
- for _, tt := range errorFormats {
- env, err := gotenv.StrictParse(strings.NewReader(tt.in))
- assert.Equal(t, tt.err, err.Error())
- assert.Equal(t, tt.out, env)
- }
- }
- func TestLoad(t *testing.T) {
- for _, tt := range fixtures {
- err := gotenv.Load(tt.filename)
- assert.Nil(t, err)
- for key, val := range tt.results {
- assert.Equal(t, val, os.Getenv(key))
- }
- os.Clearenv()
- }
- }
- func TestLoad_default(t *testing.T) {
- k := "HELLO"
- v := "world"
- err := gotenv.Load()
- assert.Nil(t, err)
- assert.Equal(t, v, os.Getenv(k))
- os.Clearenv()
- }
- func TestLoad_overriding(t *testing.T) {
- k := "HELLO"
- v := "universe"
- os.Setenv(k, v)
- err := gotenv.Load()
- assert.Nil(t, err)
- assert.Equal(t, v, os.Getenv(k))
- os.Clearenv()
- }
- func TestLoad_Env(t *testing.T) {
- err := gotenv.Load(".env.invalid")
- assert.NotNil(t, err)
- }
- func TestLoad_nonExist(t *testing.T) {
- file := ".env.not.exist"
- err := gotenv.Load(file)
- if err == nil {
- t.Errorf("gotenv.Load(`%s`) => error: `no such file or directory` != nil", file)
- }
- }
- func TestLoad_unicodeBOMFixture(t *testing.T) {
- file := "fixtures/bom.env"
- f, err := os.Open(file)
- assert.Nil(t, err)
- scanner := bufio.NewScanner(f)
- i := 1
- bom := string([]byte{239, 187, 191})
- for scanner.Scan() {
- if i == 1 {
- line := scanner.Text()
- assert.True(t, strings.HasPrefix(line, bom))
- }
- }
- }
- func TestLoad_unicodeBOM(t *testing.T) {
- file := "fixtures/bom.env"
- err := gotenv.Load(file)
- assert.Nil(t, err)
- assert.Equal(t, "UTF-8", os.Getenv("BOM"))
- os.Clearenv()
- }
- func TestMust_Load(t *testing.T) {
- for _, tt := range fixtures {
- assert.NotPanics(t, func() {
- gotenv.Must(gotenv.Load, tt.filename)
- for key, val := range tt.results {
- assert.Equal(t, val, os.Getenv(key))
- }
- os.Clearenv()
- }, "Caling gotenv.Must with gotenv.Load should NOT panic")
- }
- }
- func TestMust_Load_default(t *testing.T) {
- assert.NotPanics(t, func() {
- gotenv.Must(gotenv.Load)
- tkey := "HELLO"
- val := "world"
- assert.Equal(t, val, os.Getenv(tkey))
- os.Clearenv()
- }, "Caling gotenv.Must with gotenv.Load without arguments should NOT panic")
- }
- func TestMust_Load_nonExist(t *testing.T) {
- assert.Panics(t, func() { gotenv.Must(gotenv.Load, ".env.not.exist") }, "Caling gotenv.Must with gotenv.Load and non exist file SHOULD panic")
- }
- func TestOverLoad_overriding(t *testing.T) {
- k := "HELLO"
- v := "universe"
- os.Setenv(k, v)
- err := gotenv.OverLoad()
- assert.Nil(t, err)
- assert.Equal(t, "world", os.Getenv(k))
- os.Clearenv()
- }
- func TestMustOverLoad_nonExist(t *testing.T) {
- assert.Panics(t, func() { gotenv.Must(gotenv.OverLoad, ".env.not.exist") }, "Caling gotenv.Must with Overgotenv.Load and non exist file SHOULD panic")
- }
- func TestApply(t *testing.T) {
- os.Setenv("HELLO", "world")
- r := strings.NewReader("HELLO=universe")
- err := gotenv.Apply(r)
- assert.Nil(t, err)
- assert.Equal(t, "world", os.Getenv("HELLO"))
- os.Clearenv()
- }
- func TestOverApply(t *testing.T) {
- os.Setenv("HELLO", "world")
- r := strings.NewReader("HELLO=universe")
- err := gotenv.OverApply(r)
- assert.Nil(t, err)
- assert.Equal(t, "universe", os.Getenv("HELLO"))
- os.Clearenv()
- }
|