Skip to content

Commit 59cd1c2

Browse files
committed
added tests
1 parent 6287e5f commit 59cd1c2

File tree

7 files changed

+355
-8
lines changed

7 files changed

+355
-8
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"timestamp": "2025-12-19T20:11:19.297612+00:00",
2+
"timestamp": "2025-12-20T02:05:12.779846+00:00",
33
"exit_code": 0,
44
"tests_passed": 1,
55
"tests_failed": 0,
66
"coverage": 100.0,
7-
"test_hash": "2c8d9f54650e903b63976d5f66332c069c8bfcb4c6cfb8febc1422bc971d154b"
7+
"test_hash": "84ae36f156343d6126696a9e3e176f335777467090f50ac4f5d8ddfe9ba93cc1"
88
}

.pdd/meta/my_module_python_run.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"timestamp": "2025-12-19T20:11:19.299751+00:00",
2+
"timestamp": "2025-12-20T02:05:12.777629+00:00",
33
"exit_code": 0,
44
"tests_passed": 1,
55
"tests_failed": 0,

.pdd/meta/simple_math_python.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"pdd_version": "0.0.86",
3-
"timestamp": "2025-12-19T20:11:17.592121+00:00",
2+
"pdd_version": "0.0.87",
3+
"timestamp": "2025-12-20T02:05:18.076987+00:00",
44
"command": "test",
55
"prompt_hash": "d4357fbb6b142ab7feb3a75a575e46dae19c83cf493765bda3922bd98a3a7ea5",
66
"code_hash": null,

.pdd/meta/simple_math_python_run.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"timestamp": "2025-12-19T20:11:16.930400+00:00",
2+
"timestamp": "2025-12-20T02:05:17.514948+00:00",
33
"exit_code": 2,
44
"tests_passed": 0,
55
"tests_failed": 1,

.pdd/meta/test_broken_fixture_python_run.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"timestamp": "2025-12-19T20:11:18.296301+00:00",
2+
"timestamp": "2025-12-20T02:05:11.783112+00:00",
33
"exit_code": 1,
44
"tests_passed": 1,
55
"tests_failed": 1,

.pdd/meta/test_mixed_python_run.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"timestamp": "2025-12-19T20:11:18.782165+00:00",
2+
"timestamp": "2025-12-20T02:05:12.273198+00:00",
33
"exit_code": 1,
44
"tests_passed": 1,
55
"tests_failed": 2,

tests/test_setup_tool.py

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,3 +225,350 @@ def test_create_api_env_script_with_normal_key():
225225
finally:
226226
script_path.unlink()
227227

228+
229+
def _shell_available(shell: str) -> bool:
230+
"""Check if a shell is available on the system"""
231+
try:
232+
result = subprocess.run(
233+
['which', shell],
234+
capture_output=True,
235+
timeout=2
236+
)
237+
return result.returncode == 0
238+
except (subprocess.TimeoutExpired, FileNotFoundError):
239+
return False
240+
241+
242+
def test_create_api_env_script_with_special_characters_fish():
243+
"""
244+
Test that API keys with special characters work in fish shell scripts.
245+
246+
This test verifies that shlex.quote() works correctly with fish shell.
247+
Fish is not POSIX-compliant, so there may be edge cases where POSIX-style
248+
quoting doesn't work as expected.
249+
"""
250+
if not _shell_available('fish'):
251+
pytest.skip("fish shell not available")
252+
253+
test_keys = {
254+
'GEMINI_API_KEY': 'AIzaSyAbCdEf123456$var"quote\'backtick\\slash'
255+
}
256+
257+
script_content = create_api_env_script(test_keys, 'fish')
258+
259+
with tempfile.NamedTemporaryFile(mode='w', suffix='.fish', delete=False) as f:
260+
f.write(script_content)
261+
script_path = Path(f.name)
262+
263+
try:
264+
# Fish doesn't have a -n syntax check flag like bash/zsh
265+
# So we'll try to source it and see if it works
266+
result = subprocess.run(
267+
['fish', '-c', f'source {script_path}; exit 0'],
268+
capture_output=True,
269+
text=True,
270+
timeout=5
271+
)
272+
273+
assert result.returncode == 0, (
274+
f"Generated fish script has syntax/execution errors: {result.stderr}\n"
275+
f"Script content:\n{script_content}"
276+
)
277+
finally:
278+
script_path.unlink()
279+
280+
281+
def test_create_api_env_script_preserves_key_value_fish():
282+
"""
283+
Test that fish shell correctly preserves key values with special characters.
284+
285+
This is critical because fish has different quoting rules than POSIX shells,
286+
and shlex.quote() may not handle all cases correctly.
287+
"""
288+
if not _shell_available('fish'):
289+
pytest.skip("fish shell not available")
290+
291+
original_key = 'AIzaSyAbCdEf123456$var"quote\'backtick\\slash'
292+
test_keys = {
293+
'GEMINI_API_KEY': original_key
294+
}
295+
296+
script_content = create_api_env_script(test_keys, 'fish')
297+
298+
with tempfile.NamedTemporaryFile(mode='w', suffix='.fish', delete=False) as f:
299+
f.write(script_content)
300+
script_path = Path(f.name)
301+
302+
try:
303+
# Source the script and extract the value using fish
304+
result = subprocess.run(
305+
['fish', '-c', f'source {script_path}; python3 -c "import os; print(os.environ.get(\'GEMINI_API_KEY\', \'\'))"'],
306+
capture_output=True,
307+
text=True,
308+
timeout=5
309+
)
310+
311+
assert result.returncode == 0, (
312+
f"Failed to source fish script and read env var: {result.stderr}\n"
313+
f"Script content:\n{script_content}"
314+
)
315+
316+
extracted_key = result.stdout.strip()
317+
assert extracted_key == original_key, (
318+
f"Key value was corrupted during escaping in fish shell.\n"
319+
f"Original: {repr(original_key)}\n"
320+
f"Extracted: {repr(extracted_key)}\n"
321+
f"Script content:\n{script_content}\n"
322+
f"This indicates shlex.quote() may not work correctly with fish shell."
323+
)
324+
finally:
325+
script_path.unlink()
326+
327+
328+
def test_create_api_env_script_with_special_characters_csh():
329+
"""
330+
Test that API keys with special characters work in csh/tcsh shell scripts.
331+
332+
WARNING: csh/tcsh have fundamentally different quoting rules than POSIX shells.
333+
shlex.quote() uses POSIX single-quote syntax which may not work correctly
334+
in csh/tcsh, especially with:
335+
- Variables containing $ (variable expansion still occurs in single quotes)
336+
- Complex backslash sequences
337+
- Certain special characters
338+
339+
This test will help identify if shlex.quote() works correctly with csh/tcsh.
340+
"""
341+
# Try csh first, then tcsh
342+
shell_cmd = None
343+
shell_name = None
344+
for shell in ['csh', 'tcsh']:
345+
if _shell_available(shell):
346+
shell_cmd = shell
347+
shell_name = shell
348+
break
349+
350+
if not shell_cmd:
351+
pytest.skip("csh/tcsh not available")
352+
353+
test_keys = {
354+
'GEMINI_API_KEY': 'AIzaSyAbCdEf123456$var"quote\'backtick\\slash'
355+
}
356+
357+
script_content = create_api_env_script(test_keys, shell_name)
358+
359+
with tempfile.NamedTemporaryFile(mode='w', suffix='.csh', delete=False) as f:
360+
f.write(script_content)
361+
script_path = Path(f.name)
362+
363+
try:
364+
# csh/tcsh don't have a -n flag, so we'll try to source it
365+
# Use -f to prevent reading .cshrc/.tcshrc which might interfere
366+
result = subprocess.run(
367+
[shell_cmd, '-f', '-c', f'source {script_path}; exit 0'],
368+
capture_output=True,
369+
text=True,
370+
timeout=5
371+
)
372+
373+
assert result.returncode == 0, (
374+
f"Generated {shell_name} script has syntax/execution errors: {result.stderr}\n"
375+
f"Script content:\n{script_content}\n"
376+
f"This may indicate that shlex.quote() doesn't work correctly with {shell_name}."
377+
)
378+
finally:
379+
script_path.unlink()
380+
381+
382+
def test_create_api_env_script_preserves_key_value_csh():
383+
"""
384+
Test that csh/tcsh correctly preserves key values with special characters.
385+
386+
This is critical because csh/tcsh have fundamentally different quoting rules:
387+
- Single quotes in csh do NOT prevent variable expansion ($var still expands)
388+
- Backslash escaping works differently
389+
- The quoting mechanism is incompatible with POSIX
390+
391+
This test will likely reveal issues with using shlex.quote() for csh/tcsh.
392+
"""
393+
# Try csh first, then tcsh
394+
shell_cmd = None
395+
shell_name = None
396+
for shell in ['csh', 'tcsh']:
397+
if _shell_available(shell):
398+
shell_cmd = shell
399+
shell_name = shell
400+
break
401+
402+
if not shell_cmd:
403+
pytest.skip("csh/tcsh not available")
404+
405+
original_key = 'AIzaSyAbCdEf123456$var"quote\'backtick\\slash'
406+
test_keys = {
407+
'GEMINI_API_KEY': original_key
408+
}
409+
410+
script_content = create_api_env_script(test_keys, shell_name)
411+
412+
with tempfile.NamedTemporaryFile(mode='w', suffix='.csh', delete=False) as f:
413+
f.write(script_content)
414+
script_path = Path(f.name)
415+
416+
try:
417+
# Source the script and extract the value using csh/tcsh
418+
# Use -f to prevent reading .cshrc/.tcshrc
419+
result = subprocess.run(
420+
[shell_cmd, '-f', '-c', f'source {script_path}; python3 -c "import os; print(os.environ.get(\'GEMINI_API_KEY\', \'\'))"'],
421+
capture_output=True,
422+
text=True,
423+
timeout=5
424+
)
425+
426+
assert result.returncode == 0, (
427+
f"Failed to source {shell_name} script and read env var: {result.stderr}\n"
428+
f"Script content:\n{script_content}"
429+
)
430+
431+
extracted_key = result.stdout.strip()
432+
assert extracted_key == original_key, (
433+
f"Key value was corrupted during escaping in {shell_name} shell.\n"
434+
f"Original: {repr(original_key)}\n"
435+
f"Extracted: {repr(extracted_key)}\n"
436+
f"Script content:\n{script_content}\n"
437+
f"This indicates shlex.quote() does NOT work correctly with {shell_name}.\n"
438+
f"csh/tcsh have different quoting rules than POSIX shells."
439+
)
440+
finally:
441+
script_path.unlink()
442+
443+
444+
def test_create_api_env_script_csh_variable_expansion_issue():
445+
"""
446+
Test a specific csh/tcsh issue: variable expansion in single quotes.
447+
448+
In csh/tcsh, single quotes do NOT prevent variable expansion.
449+
This means a key containing $HOME will expand to the actual home directory
450+
path, which is incorrect behavior.
451+
452+
This test demonstrates the fundamental incompatibility between
453+
POSIX-style quoting (shlex.quote) and csh/tcsh.
454+
"""
455+
# Try csh first, then tcsh
456+
shell_cmd = None
457+
shell_name = None
458+
for shell in ['csh', 'tcsh']:
459+
if _shell_available(shell):
460+
shell_cmd = shell
461+
shell_name = shell
462+
break
463+
464+
if not shell_cmd:
465+
pytest.skip("csh/tcsh not available")
466+
467+
# Create a key that contains $HOME to test variable expansion
468+
# In POSIX shells, this should be preserved as-is
469+
# In csh/tcsh, this might expand to the actual home directory
470+
test_key = 'api_key_with_$HOME_in_it'
471+
test_keys = {
472+
'GEMINI_API_KEY': test_key
473+
}
474+
475+
script_content = create_api_env_script(test_keys, shell_name)
476+
477+
with tempfile.NamedTemporaryFile(mode='w', suffix='.csh', delete=False) as f:
478+
f.write(script_content)
479+
script_path = Path(f.name)
480+
481+
try:
482+
# Source the script and extract the value
483+
result = subprocess.run(
484+
[shell_cmd, '-f', '-c', f'source {script_path}; python3 -c "import os; print(os.environ.get(\'GEMINI_API_KEY\', \'\'))"'],
485+
capture_output=True,
486+
text=True,
487+
timeout=5
488+
)
489+
490+
assert result.returncode == 0, (
491+
f"Failed to source {shell_name} script: {result.stderr}\n"
492+
f"Script content:\n{script_content}"
493+
)
494+
495+
extracted_key = result.stdout.strip()
496+
# This test will likely FAIL, demonstrating the issue
497+
assert extracted_key == test_key, (
498+
f"Variable expansion occurred in {shell_name} despite single quotes!\n"
499+
f"Expected: {repr(test_key)}\n"
500+
f"Got: {repr(extracted_key)}\n"
501+
f"Script content:\n{script_content}\n"
502+
f"This proves that shlex.quote() (POSIX single quotes) does NOT work\n"
503+
f"correctly with csh/tcsh, which expand variables even in single quotes."
504+
)
505+
finally:
506+
script_path.unlink()
507+
508+
509+
def test_create_api_env_script_fish_edge_cases():
510+
"""
511+
Test fish shell with various edge cases that might reveal quoting issues.
512+
513+
Fish shell, while often compatible with POSIX-style quoting, may have
514+
edge cases with certain character combinations.
515+
"""
516+
if not _shell_available('fish'):
517+
pytest.skip("fish shell not available")
518+
519+
edge_cases = [
520+
'key with spaces',
521+
"key'with'single'quotes",
522+
'key"with"double"quotes',
523+
'key$with$dollars',
524+
'key\\with\\backslashes',
525+
'key`with`backticks',
526+
'key(with)parentheses',
527+
'key[with]brackets',
528+
'key{with}braces',
529+
'key;with;semicolons',
530+
'key|with|pipes',
531+
'key&with&ampersands',
532+
'key<with>redirects',
533+
'key\nwith\nnewlines',
534+
'key\twith\ttabs',
535+
]
536+
537+
for i, test_key in enumerate(edge_cases):
538+
test_keys = {
539+
'TEST_API_KEY': test_key
540+
}
541+
542+
script_content = create_api_env_script(test_keys, 'fish')
543+
544+
with tempfile.NamedTemporaryFile(mode='w', suffix=f'.fish', delete=False) as f:
545+
f.write(script_content)
546+
script_path = Path(f.name)
547+
548+
try:
549+
# Try to source it
550+
result = subprocess.run(
551+
['fish', '-c', f'source {script_path}; python3 -c "import os; print(os.environ.get(\'TEST_API_KEY\', \'\'))"'],
552+
capture_output=True,
553+
text=True,
554+
timeout=5
555+
)
556+
557+
if result.returncode != 0:
558+
pytest.fail(
559+
f"Fish shell failed with edge case {i+1}: {repr(test_key)}\n"
560+
f"Error: {result.stderr}\n"
561+
f"Script content:\n{script_content}"
562+
)
563+
564+
extracted_key = result.stdout.strip()
565+
if extracted_key != test_key:
566+
pytest.fail(
567+
f"Fish shell corrupted value for edge case {i+1}: {repr(test_key)}\n"
568+
f"Expected: {repr(test_key)}\n"
569+
f"Got: {repr(extracted_key)}\n"
570+
f"Script content:\n{script_content}"
571+
)
572+
finally:
573+
script_path.unlink()
574+

0 commit comments

Comments
 (0)