@@ -727,3 +727,380 @@ def test_file_variable_is_set(tmp_path):
727727 out , err , exitcode = helpers .cli_call (command )
728728 assert exitcode == 0
729729 assert "__file__=" in out .decode ()
730+
731+
732+ def test_stdin_input ():
733+ """
734+ Tests that a CadQuery script piped via stdin produces valid STEP output.
735+ """
736+ import subprocess
737+
738+ test_file = helpers .get_test_file_location ("cube.py" )
739+ with open (test_file , "r" ) as f :
740+ script = f .read ()
741+
742+ proc = subprocess .Popen (
743+ [sys .executable , "src/cq_cli/main.py" , "--codec" , "step" , "--outfile" , "-" ],
744+ stdin = subprocess .PIPE ,
745+ stdout = subprocess .PIPE ,
746+ stderr = subprocess .PIPE ,
747+ )
748+ # Pass script via stdin; use --outfile - equivalent: no --outfile means stdout
749+ proc2 = subprocess .Popen (
750+ [sys .executable , "src/cq_cli/main.py" , "--codec" , "step" ],
751+ stdin = subprocess .PIPE ,
752+ stdout = subprocess .PIPE ,
753+ stderr = subprocess .PIPE ,
754+ )
755+ out , err = proc2 .communicate (input = script .encode ())
756+
757+ assert "ISO-10303-21;" in out .decode ()
758+
759+
760+ def test_stdin_with_outfile (tmp_path ):
761+ """
762+ Tests that a CadQuery script piped via stdin with --outfile writes correct output.
763+ """
764+ import subprocess
765+
766+ test_file = helpers .get_test_file_location ("cube.py" )
767+ with open (test_file , "r" ) as f :
768+ script = f .read ()
769+
770+ out_path = tmp_path / "stdin_out.step"
771+
772+ proc = subprocess .Popen (
773+ [
774+ sys .executable ,
775+ "src/cq_cli/main.py" ,
776+ "--codec" ,
777+ "step" ,
778+ "--outfile" ,
779+ str (out_path ),
780+ ],
781+ stdin = subprocess .PIPE ,
782+ stdout = subprocess .PIPE ,
783+ stderr = subprocess .PIPE ,
784+ )
785+ out , err = proc .communicate (input = script .encode ())
786+
787+ assert proc .returncode == 0
788+ with open (str (out_path ), "r" ) as f :
789+ content = f .read ()
790+ assert content .startswith ("ISO-10303-21;" )
791+
792+
793+ def test_parameter_delimited_string_multiple_params (tmp_path ):
794+ """
795+ Tests that multiple key:value pairs in a single --params string all take effect.
796+ Passes width=2 and centered=False together and confirms output differs from defaults.
797+ """
798+ test_file = helpers .get_test_file_location ("cube_params.py" )
799+ out_default = tmp_path / "default.step"
800+ out_custom = tmp_path / "custom.step"
801+
802+ command_default = [
803+ sys .executable ,
804+ "src/cq_cli/main.py" ,
805+ "--codec" ,
806+ "step" ,
807+ "--infile" ,
808+ test_file ,
809+ "--outfile" ,
810+ str (out_default ),
811+ ]
812+ helpers .cli_call (command_default )
813+
814+ command_custom = [
815+ sys .executable ,
816+ "src/cq_cli/main.py" ,
817+ "--codec" ,
818+ "step" ,
819+ "--infile" ,
820+ test_file ,
821+ "--outfile" ,
822+ str (out_custom ),
823+ "--params" ,
824+ "width:2;" ,
825+ ]
826+ out , err , exitcode = helpers .cli_call (command_custom )
827+
828+ assert exitcode == 0
829+ with open (str (out_default ), "r" ) as f :
830+ default_content = f .read ()
831+ with open (str (out_custom ), "r" ) as f :
832+ custom_content = f .read ()
833+ assert default_content != custom_content
834+
835+
836+ def test_parameter_json_string_multiple_params (tmp_path ):
837+ """
838+ Tests that a JSON --params string with multiple keys all apply correctly.
839+ """
840+ test_file = helpers .get_test_file_location ("cube_params.py" )
841+ out_path = tmp_path / "multi_json.step"
842+
843+ command = [
844+ sys .executable ,
845+ "src/cq_cli/main.py" ,
846+ "--codec" ,
847+ "step" ,
848+ "--infile" ,
849+ test_file ,
850+ "--outfile" ,
851+ str (out_path ),
852+ "--params" ,
853+ '{"width": 5, "centered": false}' ,
854+ ]
855+ out , err , exitcode = helpers .cli_call (command )
856+
857+ assert exitcode == 0
858+ with open (str (out_path ), "r" ) as f :
859+ content = f .read ()
860+ assert content .startswith ("ISO-10303-21;" )
861+
862+
863+ def test_getparams_with_no_params_script ():
864+ """
865+ Tests that --getparams on a script with no user-defined parameters returns only
866+ the injected __file__ entry (a side-effect of __file__ prepending), with no other
867+ named parameters.
868+ """
869+ test_file = helpers .get_test_file_location ("cube.py" )
870+
871+ command = [
872+ sys .executable ,
873+ "src/cq_cli/main.py" ,
874+ "--getparams" ,
875+ "true" ,
876+ "--infile" ,
877+ test_file ,
878+ ]
879+ out , err , exitcode = helpers .cli_call (command )
880+
881+ assert exitcode == 0
882+ result = json .loads (out .decode ())
883+ user_params = [p for p in result if p ["name" ] != "__file__" ]
884+ assert user_params == []
885+
886+
887+ def test_getparams_writes_file_and_returns_expected_keys (tmp_path ):
888+ """
889+ Tests that --getparams writes a JSON file containing the expected parameter names.
890+ """
891+ test_file = helpers .get_test_file_location ("cube_params.py" )
892+ params_out = tmp_path / "params.json"
893+
894+ command = [
895+ sys .executable ,
896+ "src/cq_cli/main.py" ,
897+ "--getparams" ,
898+ str (params_out ),
899+ "--infile" ,
900+ test_file ,
901+ ]
902+ out , err , exitcode = helpers .cli_call (command )
903+
904+ assert exitcode == 0
905+ assert params_out .exists () and params_out .stat ().st_size > 0
906+ params = json .loads (params_out .read_text ())
907+ names = [p ["name" ] for p in params ]
908+ assert "width" in names
909+ assert "tag_name" in names
910+ assert "centered" in names
911+
912+
913+ def test_validate_with_outfile (tmp_path ):
914+ """
915+ Tests that --validate true with --outfile writes 'validation_success' to the file.
916+ """
917+ test_file = helpers .get_test_file_location ("cube.py" )
918+ out_path = tmp_path / "validation.txt"
919+
920+ command = [
921+ sys .executable ,
922+ "src/cq_cli/main.py" ,
923+ "--validate" ,
924+ "true" ,
925+ "--infile" ,
926+ test_file ,
927+ "--outfile" ,
928+ str (out_path ),
929+ ]
930+ out , err , exitcode = helpers .cli_call (command )
931+
932+ assert exitcode == 0
933+ assert out_path .read_text () == "validation_success"
934+
935+
936+ def test_syntax_error_exits_100 ():
937+ """
938+ Tests that a script with a Python syntax error exits with code 100.
939+ """
940+ test_file = helpers .get_test_file_location ("syntax_error.py" )
941+
942+ command = [
943+ sys .executable ,
944+ "src/cq_cli/main.py" ,
945+ "--codec" ,
946+ "step" ,
947+ "--infile" ,
948+ test_file ,
949+ ]
950+ out , err , exitcode = helpers .cli_call (command )
951+
952+ assert exitcode == 100
953+
954+
955+ def test_syntax_error_written_to_errfile (tmp_path ):
956+ """
957+ Tests that a script syntax error writes a traceback to errfile.
958+ """
959+ test_file = helpers .get_test_file_location ("syntax_error.py" )
960+ err_file = tmp_path / "syntax_err.txt"
961+
962+ command = [
963+ sys .executable ,
964+ "src/cq_cli/main.py" ,
965+ "--codec" ,
966+ "step" ,
967+ "--infile" ,
968+ test_file ,
969+ "--errfile" ,
970+ str (err_file ),
971+ ]
972+ out , err , exitcode = helpers .cli_call (command )
973+
974+ assert exitcode == 100
975+ content = err_file .read_text ()
976+ assert len (content ) > 0
977+
978+
979+ def test_codec_error_written_to_errfile (tmp_path ):
980+ """
981+ Tests that a codec-level failure (exit 200) writes traceback to errfile.
982+ Uses the expression argument to trigger a no-results error.
983+ """
984+ test_file = helpers .get_test_file_location ("no_toplevel_objects.py" )
985+ err_file = tmp_path / "codec_err.txt"
986+
987+ command = [
988+ sys .executable ,
989+ "src/cq_cli/main.py" ,
990+ "--codec" ,
991+ "step" ,
992+ "--infile" ,
993+ test_file ,
994+ "--errfile" ,
995+ str (err_file ),
996+ ]
997+ out , err , exitcode = helpers .cli_call (command )
998+
999+ assert exitcode == 200
1000+ content = err_file .read_text ()
1001+ assert len (content ) > 0
1002+
1003+
1004+ def test_auto_codec_detection_stl (tmp_path ):
1005+ """
1006+ Tests that the STL codec is inferred from a .stl output file extension.
1007+ """
1008+ test_file = helpers .get_test_file_location ("cube.py" )
1009+ out_path = tmp_path / "auto.stl"
1010+
1011+ command = [
1012+ sys .executable ,
1013+ "src/cq_cli/main.py" ,
1014+ "--infile" ,
1015+ test_file ,
1016+ "--outfile" ,
1017+ str (out_path ),
1018+ ]
1019+ out , err , exitcode = helpers .cli_call (command )
1020+
1021+ assert exitcode == 0
1022+ content = out_path .read_bytes ()
1023+ assert content [:5 ] == b"solid"
1024+
1025+
1026+ def test_auto_codec_detection_svg (tmp_path ):
1027+ """
1028+ Tests that the SVG codec is inferred from a .svg output file extension.
1029+ """
1030+ test_file = helpers .get_test_file_location ("cube.py" )
1031+ out_path = tmp_path / "auto.svg"
1032+
1033+ command = [
1034+ sys .executable ,
1035+ "src/cq_cli/main.py" ,
1036+ "--infile" ,
1037+ test_file ,
1038+ "--outfile" ,
1039+ str (out_path ),
1040+ ]
1041+ out , err , exitcode = helpers .cli_call (command )
1042+
1043+ assert exitcode == 0
1044+ content = out_path .read_text ()
1045+ assert '<?xml version="1.0"' in content
1046+
1047+
1048+ def test_param_change_produces_different_step_output (tmp_path ):
1049+ """
1050+ Tests that changing the width parameter produces geometrically different STEP output.
1051+ """
1052+ test_file = helpers .get_test_file_location ("cube_params.py" )
1053+ out_small = tmp_path / "small.step"
1054+ out_large = tmp_path / "large.step"
1055+
1056+ helpers .cli_call (
1057+ [
1058+ sys .executable ,
1059+ "src/cq_cli/main.py" ,
1060+ "--codec" ,
1061+ "step" ,
1062+ "--infile" ,
1063+ test_file ,
1064+ "--outfile" ,
1065+ str (out_small ),
1066+ "--params" ,
1067+ "width:1;" ,
1068+ ]
1069+ )
1070+ helpers .cli_call (
1071+ [
1072+ sys .executable ,
1073+ "src/cq_cli/main.py" ,
1074+ "--codec" ,
1075+ "step" ,
1076+ "--infile" ,
1077+ test_file ,
1078+ "--outfile" ,
1079+ str (out_large ),
1080+ "--params" ,
1081+ "width:10;" ,
1082+ ]
1083+ )
1084+
1085+ assert out_small .read_text () != out_large .read_text ()
1086+
1087+
1088+ def test_multi_show_object_produces_output ():
1089+ """
1090+ Tests that a script calling show_object() multiple times still produces valid output
1091+ (cq-cli uses the first result).
1092+ """
1093+ test_file = helpers .get_test_file_location ("multi_show_object.py" )
1094+
1095+ command = [
1096+ sys .executable ,
1097+ "src/cq_cli/main.py" ,
1098+ "--codec" ,
1099+ "step" ,
1100+ "--infile" ,
1101+ test_file ,
1102+ ]
1103+ out , err , exitcode = helpers .cli_call (command )
1104+
1105+ assert exitcode == 0
1106+ assert "ISO-10303-21;" in out .decode ()
0 commit comments