Parent directory

basic-parsing.zunit

9108 bytes
  1#!/usr/bin/env zunit
  2
  3
  4@setup {
  5  typeset -g fixture="$(_create_temp_fixture)"
  6  typeset -gA expected_vars=()
  7}
  8
  9@teardown {
 10  [[ -f "$fixture" ]] && command rm -f "$fixture"
 11  unset DOTENV_TEST_VARS DOTENV_SOURCE_VARS 2>/dev/null
 12}
 13
 14@test 'dotenv plugin loads successfully' {
 15  assert "parse_dotenv" function_exists
 16  assert "source_env" function_exists
 17}
 18
 19@test 'parse returns error for unsupported mode' {
 20  run _parse_dotenv_quiet "/dev/null" "export"
 21  assert $state equals 0
 22
 23  run _parse_dotenv_quiet "/dev/null" "test"
 24  assert $state equals 0
 25
 26  run _parse_dotenv_quiet "/dev/null" "invalid_mode"
 27  assert $state equals 1
 28}
 29
 30@test 'parse returns error for oversized file (> 10MiB)' {
 31  command truncate -s 11M "$fixture" 2>/dev/null
 32
 33  run _parse_dotenv_quiet "$fixture" "test"
 34  assert $state equals 1
 35}
 36
 37@test 'parse returns error for non-existent file' {
 38  run _parse_dotenv_quiet "/nonexistent/path/.env" "test"
 39  assert $state equals 1
 40}
 41
 42@test 'source_env loads named pipes without blocking' {
 43  local tmpdir fifo output result
 44  local child_pid writer_pid killer_pid child_rc
 45
 46  tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/dotenv.XXXXXX")"
 47  fifo="$tmpdir/.env"
 48  output="$tmpdir/output"
 49  command mkfifo "$fifo"
 50
 51  (
 52    print -r -- 'TOKEN=secret' > "$fifo"
 53  ) &
 54  writer_pid=$!
 55
 56  (
 57    ZSH_DOTENV_PROMPT=false
 58    ZSH_DOTENV_FILE="$fifo"
 59    source_env
 60    print -r -- "${TOKEN-<unset>}" > "$output"
 61  ) &
 62  child_pid=$!
 63
 64  (
 65    sleep 2
 66    kill -0 $child_pid 2>/dev/null || exit 0
 67    kill $child_pid 2>/dev/null || exit 0
 68  ) &
 69  killer_pid=$!
 70
 71  wait $child_pid
 72  child_rc=$?
 73
 74  kill $killer_pid 2>/dev/null || true
 75  kill $writer_pid 2>/dev/null || true
 76  wait $writer_pid 2>/dev/null || true
 77
 78  [[ -f "$output" ]] && result="$(<"$output")"
 79  command rm -rf "$tmpdir"
 80
 81  assert $child_rc equals 0
 82  assert "$result" equals 'secret'
 83}
 84
 85@test 'source_env rejects oversized named pipes' {
 86  run zsh -fc '
 87    source ./dotenv.plugin.zsh
 88
 89    tmpdir="$(mktemp -d "${TMPDIR:-/tmp}/dotenv.XXXXXX")" || exit 1
 90    fifo="$tmpdir/.env"
 91    command mkfifo "$fifo" || exit 1
 92
 93    cleanup() {
 94      kill $killer_pid 2>/dev/null || true
 95      kill $writer_pid 2>/dev/null || true
 96      wait $writer_pid 2>/dev/null || true
 97      command rm -rf "$tmpdir"
 98    }
 99    trap cleanup EXIT
100
101    (
102      {
103        print -rn -- "BIG="
104        command dd if=/dev/zero bs=10485761 count=1 2>/dev/null | tr "\0" a
105      } > "$fifo"
106    ) &
107    writer_pid=$!
108
109    (
110      sleep 2
111      kill -0 $$ 2>/dev/null || exit 0
112      kill $$ 2>/dev/null || exit 0
113    ) &
114    killer_pid=$!
115
116    ZSH_DOTENV_PROMPT=false
117    ZSH_DOTENV_FILE="$fifo"
118    source_env >/dev/null 2>&1
119  '
120
121  assert $state equals 1
122}
123
124@test 'parse basic variable assignment' {
125  > "$fixture" <<'EOF'
126# Basic assignments
127BASIC=basic
128
129# previous line intentionally left blank
130AFTER_LINE=after_line
131EOF
132
133  expected_vars=(
134    BASIC 'basic'
135    AFTER_LINE 'after_line'
136  )
137
138  _parse_dotenv_test "$fixture"
139
140  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
141}
142
143@test 'parse empty values' {
144  > "$fixture" <<'EOF'
145# Empty values
146EMPTY=
147EMPTY_SINGLE_QUOTES=''
148EMPTY_DOUBLE_QUOTES=""
149EOF
150
151  expected_vars=(
152    EMPTY ''
153    EMPTY_SINGLE_QUOTES ''
154    EMPTY_DOUBLE_QUOTES ''
155  )
156
157  _parse_dotenv_test "$fixture"
158
159  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
160}
161
162@test 'parse single quoted values' {
163  > "$fixture" <<'EOF'
164# Single quotes (literal, no expansion)
165SINGLE_QUOTES='single_quotes'
166SINGLE_QUOTES_SPACED='    single quotes    '
167DONT_EXPAND_SQUOTED='dontexpand\nnewlines'
168EOF
169
170  expected_vars=(
171    SINGLE_QUOTES 'single_quotes'
172    SINGLE_QUOTES_SPACED '    single quotes    '
173    DONT_EXPAND_SQUOTED 'dontexpand\nnewlines'
174  )
175
176  _parse_dotenv_test "$fixture"
177
178  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
179}
180
181@test 'parse double quoted values' {
182  > "$fixture" <<'EOF'
183# Double quotes (with escapes)
184DOUBLE_QUOTES="double_quotes"
185DOUBLE_QUOTES_SPACED="    double quotes    "
186EXPAND_NEWLINES="expand\nnew\nlines"
187EOF
188
189  expected_vars=(
190    DOUBLE_QUOTES 'double_quotes'
191    DOUBLE_QUOTES_SPACED '    double quotes    '
192    EXPAND_NEWLINES "expand\nnew\nlines"
193  )
194
195  _parse_dotenv_test "$fixture"
196
197  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
198}
199
200@test 'parse unquoted values' {
201  > "$fixture" <<'EOF'
202# Unquoted (no escape expansion)
203DONT_EXPAND_UNQUOTED=dontexpand\\nnewlines
204EOF
205
206
207  expected_vars=(
208    DONT_EXPAND_UNQUOTED 'dontexpandnnewlines'
209  )
210
211  _parse_dotenv_test "$fixture"
212
213  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
214}
215
216@test 'parse quotes inside quotes' {
217  > "$fixture" <<'EOF'
218# Quotes inside quotes
219DOUBLE_QUOTES_INSIDE_SINGLE='double "quotes" work inside single quotes'
220SINGLE_QUOTES_INSIDE_DOUBLE="single 'quotes' work inside double quotes"
221EOF
222
223  expected_vars=(
224    DOUBLE_QUOTES_INSIDE_SINGLE 'double "quotes" work inside single quotes'
225    SINGLE_QUOTES_INSIDE_DOUBLE "single 'quotes' work inside double quotes"
226  )
227
228  _parse_dotenv_test "$fixture"
229
230  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
231}
232
233@test 'parse inline comments' {
234  > "$fixture" <<'EOF'
235# Comments
236# COMMENTS=work
237INLINE_COMMENTS_SINGLE_QUOTES='inline comments outside of #singlequotes' # work
238INLINE_COMMENTS_DOUBLE_QUOTES="inline comments outside of #doublequotes" # work
239INLINE_COMMENTS_UNQUOTED=value # work
240EOF
241
242  expected_vars=(
243    INLINE_COMMENTS_SINGLE_QUOTES 'inline comments outside of #singlequotes'
244    INLINE_COMMENTS_DOUBLE_QUOTES 'inline comments outside of #doublequotes'
245    INLINE_COMMENTS_UNQUOTED 'value'
246  )
247
248  _parse_dotenv_test "$fixture"
249
250  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
251}
252
253@test 'parse ignores non-assignment commands with assignment-looking arguments' {
254  > "$fixture" <<'EOF'
255print SHOULD_NOT_PARSE=value
256EOF
257
258  _parse_dotenv_test "$fixture"
259
260  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
261}
262
263@test 'parse special characters' {
264  > "$fixture" <<'EOF'
265# Special characters
266EQUAL_SIGNS=equals==
267RETAIN_INNER_QUOTES_AS_STRING='{"foo": "bar"}'
268USEREMAIL=therealnerdybeast@example.tld
269EOF
270
271  expected_vars=(
272    EQUAL_SIGNS 'equals=='
273    RETAIN_INNER_QUOTES_AS_STRING '{"foo": "bar"}'
274    USEREMAIL 'therealnerdybeast@example.tld'
275  )
276
277  _parse_dotenv_test "$fixture"
278
279  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
280}
281
282@test 'parse multiline values with mixed quotes' {
283  > "$fixture" <<'EOF'
284# Multiline values with double quotes
285MULTI_DOUBLE_QUOTED="THIS
286IS
287A
288MULTILINE
289STRING"
290
291
292# Multiline values with single quotes
293MULTI_SINGLE_QUOTED='THIS
294IS
295A
296MULTILINE
297STRING'
298
299# Multiline PEM certificate
300MULTI_PEM_DOUBLE_QUOTED="-----BEGIN PUBLIC KEY-----
301MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
302LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
303bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
304kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
305u4QuUoobAgMBAAE=
306-----END PUBLIC KEY-----"
307EOF
308
309  expected_vars=(
310    MULTI_DOUBLE_QUOTED $'THIS\nIS\nA\nMULTILINE\nSTRING'
311    MULTI_SINGLE_QUOTED $'THIS\nIS\nA\nMULTILINE\nSTRING'
312    MULTI_PEM_DOUBLE_QUOTED $'-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u\nLgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/\nbTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/\nkKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V\nu4QuUoobAgMBAAE=\n-----END PUBLIC KEY-----'
313  )
314
315  _parse_dotenv_test "$fixture"
316
317  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
318}
319
320@test 'parse export syntax' {
321  > "$fixture" <<'EOF'
322# Exported variables
323export EXPORTED_VAR=exported_value
324export EXPORTED_EMPTY=
325EOF
326
327  expected_vars=(
328    EXPORTED_VAR 'exported_value'
329    EXPORTED_EMPTY ''
330  )
331
332  _parse_dotenv_test "$fixture"
333
334  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
335}
336
337@test 'parse in-file variable expansion' {
338  > "$fixture" <<'EOF'
339# Variable expansion (in-file forward references)
340BASE_URL=https://api.example.com
341API_ENDPOINT="${BASE_URL}/v1"
342FULL_ENDPOINT=$BASE_URL/v2/users
343COMBINED="${BASE_URL}_suffix"
344EOF
345
346  expected_vars=(
347    BASE_URL 'https://api.example.com'
348    API_ENDPOINT 'https://api.example.com/v1'
349    FULL_ENDPOINT 'https://api.example.com/v2/users'
350    COMBINED 'https://api.example.com_suffix'
351  )
352
353  _parse_dotenv_test "$fixture"
354
355  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
356}
357
358@test 'parse in-file variable expansion prefers the longest matching variable name' {
359  > "$fixture" <<'EOF'
360A=1
361ABC=2
362X=$ABC
363Y=${ABC}
364Z=$ABCD
365EOF
366
367  expected_vars=(
368    A '1'
369    ABC '2'
370    X '2'
371    Y '2'
372    Z ''
373  )
374
375  _parse_dotenv_test "$fixture"
376
377  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
378}
379
380@test 'parse preserves escaped dollar signs before variable expansion' {
381  > "$fixture" <<'EOF'
382BAR=expanded
383ESCAPED_UNQUOTED=foo\$BAR
384ESCAPED_DOUBLE="foo\$BAR"
385ESCAPED_BRACED="\${BAR}"
386EOF
387
388  expected_vars=(
389    BAR 'expanded'
390    ESCAPED_UNQUOTED 'foo$BAR'
391    ESCAPED_DOUBLE 'foo$BAR'
392    ESCAPED_BRACED '${BAR}'
393  )
394
395  _parse_dotenv_test "$fixture"
396
397  assert "DOTENV_TEST_VARS" var_same_as "expected_vars"
398}