@@ -451,6 +451,170 @@ def test_sell_insufficient_balance_json(tmp_path, monkeypatch):
451451 assert "Insufficient" in result .output
452452
453453
454+ def test_buy_slippage_negative ():
455+ """Buy with negative slippage exits with error."""
456+ result = runner .invoke (
457+ app , ["--rpc" , "https://fake.rpc" , "buy" , "--slippage" , "-5" , _FAKE_MINT , "0.01" ]
458+ )
459+ assert result .exit_code != 0
460+ assert "Slippage must be between 0 and 100" in result .output
461+
462+
463+ def test_buy_slippage_above_100 ():
464+ """Buy with slippage above 100 exits with error."""
465+ result = runner .invoke (
466+ app , ["--rpc" , "https://fake.rpc" , "buy" , "--slippage" , "999" , _FAKE_MINT , "0.01" ]
467+ )
468+ assert result .exit_code != 0
469+ assert "Slippage must be between 0 and 100" in result .output
470+
471+
472+ def test_sell_slippage_negative ():
473+ """Sell with negative slippage exits with error."""
474+ result = runner .invoke (
475+ app , ["--rpc" , "https://fake.rpc" , "sell" , "--slippage" , "-5" , _FAKE_MINT , "all" ]
476+ )
477+ assert result .exit_code != 0
478+ assert "Slippage must be between 0 and 100" in result .output
479+
480+
481+ def test_sell_slippage_above_100 ():
482+ """Sell with slippage above 100 exits with error."""
483+ result = runner .invoke (
484+ app , ["--rpc" , "https://fake.rpc" , "sell" , "--slippage" , "999" , _FAKE_MINT , "all" ]
485+ )
486+ assert result .exit_code != 0
487+ assert "Slippage must be between 0 and 100" in result .output
488+
489+
490+ def test_buy_slippage_zero (tmp_path , monkeypatch ):
491+ """Buy with slippage=0 is valid (boundary)."""
492+ monkeypatch .setenv ("XDG_CONFIG_HOME" , str (tmp_path ))
493+ monkeypatch .setenv ("PUMPFUN_PASSWORD" , "testpass" )
494+
495+ from solders .keypair import Keypair
496+
497+ from pumpfun_cli .crypto import encrypt_keypair
498+
499+ config_dir = tmp_path / "pumpfun-cli"
500+ config_dir .mkdir ()
501+ encrypt_keypair (Keypair (), "testpass" , config_dir / "wallet.enc" )
502+
503+ with patch ("pumpfun_cli.commands.trade.buy_token" , new_callable = AsyncMock ) as mock_buy :
504+ mock_buy .return_value = {
505+ "action" : "buy" ,
506+ "mint" : _FAKE_MINT ,
507+ "sol_spent" : 0.01 ,
508+ "tokens_received" : 100.0 ,
509+ "signature" : "sig" ,
510+ "explorer" : "https://solscan.io/tx/sig" ,
511+ }
512+
513+ result = runner .invoke (
514+ app ,
515+ ["--json" , "--rpc" , "http://rpc" , "buy" , "--slippage" , "0" , _FAKE_MINT , "0.01" ],
516+ )
517+
518+ assert result .exit_code == 0
519+ assert "Slippage must be between" not in result .output
520+
521+
522+ def test_buy_slippage_100 (tmp_path , monkeypatch ):
523+ """Buy with slippage=100 is valid (boundary)."""
524+ monkeypatch .setenv ("XDG_CONFIG_HOME" , str (tmp_path ))
525+ monkeypatch .setenv ("PUMPFUN_PASSWORD" , "testpass" )
526+
527+ from solders .keypair import Keypair
528+
529+ from pumpfun_cli .crypto import encrypt_keypair
530+
531+ config_dir = tmp_path / "pumpfun-cli"
532+ config_dir .mkdir ()
533+ encrypt_keypair (Keypair (), "testpass" , config_dir / "wallet.enc" )
534+
535+ with patch ("pumpfun_cli.commands.trade.buy_token" , new_callable = AsyncMock ) as mock_buy :
536+ mock_buy .return_value = {
537+ "action" : "buy" ,
538+ "mint" : _FAKE_MINT ,
539+ "sol_spent" : 0.01 ,
540+ "tokens_received" : 100.0 ,
541+ "signature" : "sig" ,
542+ "explorer" : "https://solscan.io/tx/sig" ,
543+ }
544+
545+ result = runner .invoke (
546+ app ,
547+ ["--json" , "--rpc" , "http://rpc" , "buy" , "--slippage" , "100" , _FAKE_MINT , "0.01" ],
548+ )
549+
550+ assert result .exit_code == 0
551+ assert "Slippage must be between" not in result .output
552+
553+
554+ def test_sell_slippage_zero (tmp_path , monkeypatch ):
555+ """Sell with slippage=0 is valid (boundary)."""
556+ monkeypatch .setenv ("XDG_CONFIG_HOME" , str (tmp_path ))
557+ monkeypatch .setenv ("PUMPFUN_PASSWORD" , "testpass" )
558+
559+ from solders .keypair import Keypair
560+
561+ from pumpfun_cli .crypto import encrypt_keypair
562+
563+ config_dir = tmp_path / "pumpfun-cli"
564+ config_dir .mkdir ()
565+ encrypt_keypair (Keypair (), "testpass" , config_dir / "wallet.enc" )
566+
567+ with patch ("pumpfun_cli.commands.trade.sell_token" , new_callable = AsyncMock ) as mock_sell :
568+ mock_sell .return_value = {
569+ "action" : "sell" ,
570+ "mint" : _FAKE_MINT ,
571+ "sol_received" : 0.01 ,
572+ "tokens_sold" : 100.0 ,
573+ "signature" : "sig" ,
574+ "explorer" : "https://solscan.io/tx/sig" ,
575+ }
576+
577+ result = runner .invoke (
578+ app ,
579+ ["--json" , "--rpc" , "http://rpc" , "sell" , "--slippage" , "0" , _FAKE_MINT , "all" ],
580+ )
581+
582+ assert result .exit_code == 0
583+ assert "Slippage must be between" not in result .output
584+
585+
586+ def test_sell_slippage_100 (tmp_path , monkeypatch ):
587+ """Sell with slippage=100 is valid (boundary)."""
588+ monkeypatch .setenv ("XDG_CONFIG_HOME" , str (tmp_path ))
589+ monkeypatch .setenv ("PUMPFUN_PASSWORD" , "testpass" )
590+
591+ from solders .keypair import Keypair
592+
593+ from pumpfun_cli .crypto import encrypt_keypair
594+
595+ config_dir = tmp_path / "pumpfun-cli"
596+ config_dir .mkdir ()
597+ encrypt_keypair (Keypair (), "testpass" , config_dir / "wallet.enc" )
598+
599+ with patch ("pumpfun_cli.commands.trade.sell_token" , new_callable = AsyncMock ) as mock_sell :
600+ mock_sell .return_value = {
601+ "action" : "sell" ,
602+ "mint" : _FAKE_MINT ,
603+ "sol_received" : 0.01 ,
604+ "tokens_sold" : 100.0 ,
605+ "signature" : "sig" ,
606+ "explorer" : "https://solscan.io/tx/sig" ,
607+ }
608+
609+ result = runner .invoke (
610+ app ,
611+ ["--json" , "--rpc" , "http://rpc" , "sell" , "--slippage" , "100" , _FAKE_MINT , "all" ],
612+ )
613+
614+ assert result .exit_code == 0
615+ assert "Slippage must be between" not in result .output
616+
617+
454618def test_buy_json_output_has_expected_keys (tmp_path , monkeypatch ):
455619 """Verify JSON buy output has all expected keys."""
456620 monkeypatch .setenv ("XDG_CONFIG_HOME" , str (tmp_path ))
0 commit comments