godotenv_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package godotenv
  2. import (
  3. "os"
  4. "testing"
  5. )
  6. var noopPresets = make(map[string]string)
  7. func parseAndCompare(t *testing.T, rawEnvLine string, expectedKey string, expectedValue string) {
  8. key, value, _ := parseLine(rawEnvLine)
  9. if key != expectedKey || value != expectedValue {
  10. t.Errorf("Expected '%v' to parse as '%v' => '%v', got '%v' => '%v' instead", rawEnvLine, expectedKey, expectedValue, key, value)
  11. }
  12. }
  13. func loadEnvAndCompareValues(t *testing.T, loader func(files ...string) error, envFileName string, expectedValues map[string]string, presets map[string]string) {
  14. // first up, clear the env
  15. os.Clearenv()
  16. for k, v := range presets {
  17. os.Setenv(k, v)
  18. }
  19. err := loader(envFileName)
  20. if err != nil {
  21. t.Fatalf("Error loading %v", envFileName)
  22. }
  23. for k := range expectedValues {
  24. envValue := os.Getenv(k)
  25. v := expectedValues[k]
  26. if envValue != v {
  27. t.Errorf("Mismatch for key '%v': expected '%v' got '%v'", k, v, envValue)
  28. }
  29. }
  30. }
  31. func TestLoadWithNoArgsLoadsDotEnv(t *testing.T) {
  32. err := Load()
  33. pathError := err.(*os.PathError)
  34. if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" {
  35. t.Errorf("Didn't try and open .env by default")
  36. }
  37. }
  38. func TestOverloadWithNoArgsOverloadsDotEnv(t *testing.T) {
  39. err := Overload()
  40. pathError := err.(*os.PathError)
  41. if pathError == nil || pathError.Op != "open" || pathError.Path != ".env" {
  42. t.Errorf("Didn't try and open .env by default")
  43. }
  44. }
  45. func TestLoadFileNotFound(t *testing.T) {
  46. err := Load("somefilethatwillneverexistever.env")
  47. if err == nil {
  48. t.Error("File wasn't found but Load didn't return an error")
  49. }
  50. }
  51. func TestOverloadFileNotFound(t *testing.T) {
  52. err := Overload("somefilethatwillneverexistever.env")
  53. if err == nil {
  54. t.Error("File wasn't found but Overload didn't return an error")
  55. }
  56. }
  57. func TestReadPlainEnv(t *testing.T) {
  58. envFileName := "fixtures/plain.env"
  59. expectedValues := map[string]string{
  60. "OPTION_A": "1",
  61. "OPTION_B": "2",
  62. "OPTION_C": "3",
  63. "OPTION_D": "4",
  64. "OPTION_E": "5",
  65. }
  66. envMap, err := Read(envFileName)
  67. if err != nil {
  68. t.Error("Error reading file")
  69. }
  70. if len(envMap) != len(expectedValues) {
  71. t.Error("Didn't get the right size map back")
  72. }
  73. for key, value := range expectedValues {
  74. if envMap[key] != value {
  75. t.Error("Read got one of the keys wrong")
  76. }
  77. }
  78. }
  79. func TestLoadDoesNotOverride(t *testing.T) {
  80. envFileName := "fixtures/plain.env"
  81. // ensure NO overload
  82. presets := map[string]string{
  83. "OPTION_A": "do_not_override",
  84. }
  85. expectedValues := map[string]string{
  86. "OPTION_A": "do_not_override",
  87. }
  88. loadEnvAndCompareValues(t, Load, envFileName, expectedValues, presets)
  89. }
  90. func TestOveroadDoesOverride(t *testing.T) {
  91. envFileName := "fixtures/plain.env"
  92. // ensure NO overload
  93. presets := map[string]string{
  94. "OPTION_A": "do_not_override",
  95. }
  96. expectedValues := map[string]string{
  97. "OPTION_A": "1",
  98. }
  99. loadEnvAndCompareValues(t, Overload, envFileName, expectedValues, presets)
  100. }
  101. func TestLoadPlainEnv(t *testing.T) {
  102. envFileName := "fixtures/plain.env"
  103. expectedValues := map[string]string{
  104. "OPTION_A": "1",
  105. "OPTION_B": "2",
  106. "OPTION_C": "3",
  107. "OPTION_D": "4",
  108. "OPTION_E": "5",
  109. }
  110. loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
  111. }
  112. func TestLoadExportedEnv(t *testing.T) {
  113. envFileName := "fixtures/exported.env"
  114. expectedValues := map[string]string{
  115. "OPTION_A": "2",
  116. "OPTION_B": "\n",
  117. }
  118. loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
  119. }
  120. func TestLoadEqualsEnv(t *testing.T) {
  121. envFileName := "fixtures/equals.env"
  122. expectedValues := map[string]string{
  123. "OPTION_A": "postgres://localhost:5432/database?sslmode=disable",
  124. }
  125. loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
  126. }
  127. func TestLoadQuotedEnv(t *testing.T) {
  128. envFileName := "fixtures/quoted.env"
  129. expectedValues := map[string]string{
  130. "OPTION_A": "1",
  131. "OPTION_B": "2",
  132. "OPTION_C": "",
  133. "OPTION_D": "\n",
  134. "OPTION_E": "1",
  135. "OPTION_F": "2",
  136. "OPTION_G": "",
  137. "OPTION_H": "\n",
  138. }
  139. loadEnvAndCompareValues(t, Load, envFileName, expectedValues, noopPresets)
  140. }
  141. func TestActualEnvVarsAreLeftAlone(t *testing.T) {
  142. os.Clearenv()
  143. os.Setenv("OPTION_A", "actualenv")
  144. _ = Load("fixtures/plain.env")
  145. if os.Getenv("OPTION_A") != "actualenv" {
  146. t.Error("An ENV var set earlier was overwritten")
  147. }
  148. }
  149. func TestParsing(t *testing.T) {
  150. // unquoted values
  151. parseAndCompare(t, "FOO=bar", "FOO", "bar")
  152. // parses values with spaces around equal sign
  153. parseAndCompare(t, "FOO =bar", "FOO", "bar")
  154. parseAndCompare(t, "FOO= bar", "FOO", "bar")
  155. // parses double quoted values
  156. parseAndCompare(t, "FOO=\"bar\"", "FOO", "bar")
  157. // parses single quoted values
  158. parseAndCompare(t, "FOO='bar'", "FOO", "bar")
  159. // parses escaped double quotes
  160. parseAndCompare(t, "FOO=escaped\\\"bar\"", "FOO", "escaped\"bar")
  161. // parses yaml style options
  162. parseAndCompare(t, "OPTION_A: 1", "OPTION_A", "1")
  163. // parses export keyword
  164. parseAndCompare(t, "export OPTION_A=2", "OPTION_A", "2")
  165. parseAndCompare(t, "export OPTION_B='\\n'", "OPTION_B", "\n")
  166. // it 'expands newlines in quoted strings' do
  167. // expect(env('FOO="bar\nbaz"')).to eql('FOO' => "bar\nbaz")
  168. parseAndCompare(t, "FOO=\"bar\\nbaz\"", "FOO", "bar\nbaz")
  169. // it 'parses varibales with "." in the name' do
  170. // expect(env('FOO.BAR=foobar')).to eql('FOO.BAR' => 'foobar')
  171. parseAndCompare(t, "FOO.BAR=foobar", "FOO.BAR", "foobar")
  172. // it 'parses varibales with several "=" in the value' do
  173. // expect(env('FOO=foobar=')).to eql('FOO' => 'foobar=')
  174. parseAndCompare(t, "FOO=foobar=", "FOO", "foobar=")
  175. // it 'strips unquoted values' do
  176. // expect(env('foo=bar ')).to eql('foo' => 'bar') # not 'bar '
  177. parseAndCompare(t, "FOO=bar ", "FOO", "bar")
  178. // it 'ignores inline comments' do
  179. // expect(env("foo=bar # this is foo")).to eql('foo' => 'bar')
  180. parseAndCompare(t, "FOO=bar # this is foo", "FOO", "bar")
  181. // it 'allows # in quoted value' do
  182. // expect(env('foo="bar#baz" # comment')).to eql('foo' => 'bar#baz')
  183. parseAndCompare(t, "FOO=\"bar#baz\" # comment", "FOO", "bar#baz")
  184. parseAndCompare(t, "FOO='bar#baz' # comment", "FOO", "bar#baz")
  185. parseAndCompare(t, "FOO=\"bar#baz#bang\" # comment", "FOO", "bar#baz#bang")
  186. // it 'parses # in quoted values' do
  187. // expect(env('foo="ba#r"')).to eql('foo' => 'ba#r')
  188. // expect(env("foo='ba#r'")).to eql('foo' => 'ba#r')
  189. parseAndCompare(t, "FOO=\"ba#r\"", "FOO", "ba#r")
  190. parseAndCompare(t, "FOO='ba#r'", "FOO", "ba#r")
  191. // it 'throws an error if line format is incorrect' do
  192. // expect{env('lol$wut')}.to raise_error(Dotenv::FormatError)
  193. badlyFormattedLine := "lol$wut"
  194. _, _, err := parseLine(badlyFormattedLine)
  195. if err == nil {
  196. t.Errorf("Expected \"%v\" to return error, but it didn't", badlyFormattedLine)
  197. }
  198. }
  199. func TestLinesToIgnore(t *testing.T) {
  200. // it 'ignores empty lines' do
  201. // expect(env("\n \t \nfoo=bar\n \nfizz=buzz")).to eql('foo' => 'bar', 'fizz' => 'buzz')
  202. if !isIgnoredLine("\n") {
  203. t.Error("Line with nothing but line break wasn't ignored")
  204. }
  205. if !isIgnoredLine("\t\t ") {
  206. t.Error("Line full of whitespace wasn't ignored")
  207. }
  208. // it 'ignores comment lines' do
  209. // expect(env("\n\n\n # HERE GOES FOO \nfoo=bar")).to eql('foo' => 'bar')
  210. if !isIgnoredLine("# comment") {
  211. t.Error("Comment wasn't ignored")
  212. }
  213. if !isIgnoredLine("\t#comment") {
  214. t.Error("Indented comment wasn't ignored")
  215. }
  216. // make sure we're not getting false positives
  217. if isIgnoredLine("export OPTION_B='\\n'") {
  218. t.Error("ignoring a perfectly valid line to parse")
  219. }
  220. }
  221. func TestErrorReadDirectory(t *testing.T) {
  222. envFileName := "fixtures/"
  223. envMap, err := Read(envFileName)
  224. if err == nil {
  225. t.Errorf("Expected error, got %v", envMap)
  226. }
  227. }
  228. func TestErrorParsing(t *testing.T) {
  229. envFileName := "fixtures/invalid1.env"
  230. envMap, err := Read(envFileName)
  231. if err == nil {
  232. t.Errorf("Expected error, got %v", envMap)
  233. }
  234. }