kubectl-mcp-server 1.16.0__py3-none-any.whl → 1.17.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tests/test_prompts.py CHANGED
@@ -10,11 +10,42 @@ This module tests all FastMCP 3 prompts including:
10
10
  - debug_networking
11
11
  - scale_application
12
12
  - upgrade_cluster
13
+
14
+ Also tests the configurable prompts system:
15
+ - Custom prompt loading from TOML
16
+ - Template rendering with Mustache syntax
17
+ - Built-in prompts (cluster-health-check, debug-workload, etc.)
18
+ - Prompt validation and merging
13
19
  """
14
20
 
15
21
  import pytest
22
+ import tempfile
23
+ import os
16
24
  from unittest.mock import patch, MagicMock
17
25
 
26
+ from kubectl_mcp_tool.prompts.custom import (
27
+ CustomPrompt,
28
+ PromptArgument,
29
+ PromptMessage,
30
+ render_prompt,
31
+ load_prompts_from_config,
32
+ load_prompts_from_toml_file,
33
+ validate_prompt_args,
34
+ apply_defaults,
35
+ get_prompt_schema,
36
+ )
37
+ from kubectl_mcp_tool.prompts.builtin import (
38
+ BUILTIN_PROMPTS,
39
+ get_builtin_prompts,
40
+ get_builtin_prompt_by_name,
41
+ CLUSTER_HEALTH_CHECK,
42
+ DEBUG_WORKLOAD,
43
+ RESOURCE_USAGE,
44
+ SECURITY_POSTURE,
45
+ DEPLOYMENT_CHECKLIST,
46
+ INCIDENT_RESPONSE,
47
+ )
48
+
18
49
 
19
50
  class TestTroubleshootWorkloadPrompt:
20
51
  """Tests for troubleshoot_workload prompt."""
@@ -534,3 +565,688 @@ class TestPromptContent:
534
565
  for statement in action_statements:
535
566
  # Verify each statement is a valid action prompt
536
567
  assert "now" in statement.lower() or "begin" in statement.lower() or "start" in statement.lower()
568
+
569
+
570
+ # =============================================================================
571
+ # Configurable Prompts System Tests
572
+ # =============================================================================
573
+
574
+
575
+ class TestPromptArgument:
576
+ """Tests for PromptArgument dataclass."""
577
+
578
+ @pytest.mark.unit
579
+ def test_create_required_argument(self):
580
+ """Test creating a required prompt argument."""
581
+ arg = PromptArgument(
582
+ name="pod_name",
583
+ description="Name of the pod",
584
+ required=True
585
+ )
586
+ assert arg.name == "pod_name"
587
+ assert arg.description == "Name of the pod"
588
+ assert arg.required is True
589
+ assert arg.default == ""
590
+
591
+ @pytest.mark.unit
592
+ def test_create_optional_argument_with_default(self):
593
+ """Test creating an optional argument with default value."""
594
+ arg = PromptArgument(
595
+ name="namespace",
596
+ description="Kubernetes namespace",
597
+ required=False,
598
+ default="default"
599
+ )
600
+ assert arg.name == "namespace"
601
+ assert arg.required is False
602
+ assert arg.default == "default"
603
+
604
+
605
+ class TestPromptMessage:
606
+ """Tests for PromptMessage dataclass."""
607
+
608
+ @pytest.mark.unit
609
+ def test_create_user_message(self):
610
+ """Test creating a user message."""
611
+ msg = PromptMessage(
612
+ role="user",
613
+ content="Debug the pod in namespace default"
614
+ )
615
+ assert msg.role == "user"
616
+ assert msg.content == "Debug the pod in namespace default"
617
+
618
+ @pytest.mark.unit
619
+ def test_create_assistant_message(self):
620
+ """Test creating an assistant message."""
621
+ msg = PromptMessage(
622
+ role="assistant",
623
+ content="I'll help you debug the pod."
624
+ )
625
+ assert msg.role == "assistant"
626
+
627
+
628
+ class TestCustomPrompt:
629
+ """Tests for CustomPrompt dataclass."""
630
+
631
+ @pytest.mark.unit
632
+ def test_create_simple_prompt(self):
633
+ """Test creating a simple custom prompt."""
634
+ prompt = CustomPrompt(
635
+ name="test-prompt",
636
+ title="Test Prompt",
637
+ description="A test prompt"
638
+ )
639
+ assert prompt.name == "test-prompt"
640
+ assert prompt.title == "Test Prompt"
641
+ assert prompt.description == "A test prompt"
642
+ assert prompt.arguments == []
643
+ assert prompt.messages == []
644
+
645
+ @pytest.mark.unit
646
+ def test_create_prompt_with_arguments_and_messages(self):
647
+ """Test creating a prompt with arguments and messages."""
648
+ prompt = CustomPrompt(
649
+ name="debug-pod",
650
+ title="Debug Pod",
651
+ description="Debug a Kubernetes pod",
652
+ arguments=[
653
+ PromptArgument(name="pod_name", required=True),
654
+ PromptArgument(name="namespace", default="default"),
655
+ ],
656
+ messages=[
657
+ PromptMessage(role="user", content="Debug pod {{pod_name}}"),
658
+ ]
659
+ )
660
+ assert len(prompt.arguments) == 2
661
+ assert len(prompt.messages) == 1
662
+ assert prompt.arguments[0].name == "pod_name"
663
+
664
+
665
+ class TestRenderPrompt:
666
+ """Tests for render_prompt function."""
667
+
668
+ @pytest.mark.unit
669
+ def test_simple_variable_substitution(self):
670
+ """Test basic variable substitution."""
671
+ prompt = CustomPrompt(
672
+ name="test",
673
+ description="Test",
674
+ messages=[
675
+ PromptMessage(role="user", content="Debug pod {{pod_name}} in {{namespace}}")
676
+ ]
677
+ )
678
+ result = render_prompt(prompt, {"pod_name": "nginx", "namespace": "production"})
679
+ assert len(result) == 1
680
+ assert result[0].content == "Debug pod nginx in production"
681
+ assert result[0].role == "user"
682
+
683
+ @pytest.mark.unit
684
+ def test_conditional_section_shown_when_true(self):
685
+ """Test conditional section is shown when variable is truthy."""
686
+ prompt = CustomPrompt(
687
+ name="test",
688
+ description="Test",
689
+ messages=[
690
+ PromptMessage(
691
+ role="user",
692
+ content="Check pods{{#namespace}} in namespace {{namespace}}{{/namespace}}."
693
+ )
694
+ ]
695
+ )
696
+ result = render_prompt(prompt, {"namespace": "production"})
697
+ assert "in namespace production" in result[0].content
698
+
699
+ @pytest.mark.unit
700
+ def test_conditional_section_hidden_when_false(self):
701
+ """Test conditional section is hidden when variable is falsy."""
702
+ prompt = CustomPrompt(
703
+ name="test",
704
+ description="Test",
705
+ messages=[
706
+ PromptMessage(
707
+ role="user",
708
+ content="Check pods{{#namespace}} in namespace {{namespace}}{{/namespace}}."
709
+ )
710
+ ]
711
+ )
712
+ result = render_prompt(prompt, {})
713
+ assert "in namespace" not in result[0].content
714
+ assert result[0].content == "Check pods."
715
+
716
+ @pytest.mark.unit
717
+ def test_conditional_section_with_false_value(self):
718
+ """Test conditional section is hidden when variable is 'false'."""
719
+ prompt = CustomPrompt(
720
+ name="test",
721
+ description="Test",
722
+ messages=[
723
+ PromptMessage(
724
+ role="user",
725
+ content="{{#check_metrics}}Include metrics analysis.{{/check_metrics}}"
726
+ )
727
+ ]
728
+ )
729
+ result = render_prompt(prompt, {"check_metrics": "false"})
730
+ assert "Include metrics" not in result[0].content
731
+
732
+ @pytest.mark.unit
733
+ def test_inverse_section_shown_when_false(self):
734
+ """Test inverse section is shown when variable is falsy."""
735
+ prompt = CustomPrompt(
736
+ name="test",
737
+ description="Test",
738
+ messages=[
739
+ PromptMessage(
740
+ role="user",
741
+ content="{{^namespace}}All namespaces{{/namespace}}{{#namespace}}Namespace: {{namespace}}{{/namespace}}"
742
+ )
743
+ ]
744
+ )
745
+ result = render_prompt(prompt, {})
746
+ assert "All namespaces" in result[0].content
747
+
748
+ @pytest.mark.unit
749
+ def test_inverse_section_hidden_when_true(self):
750
+ """Test inverse section is hidden when variable is truthy."""
751
+ prompt = CustomPrompt(
752
+ name="test",
753
+ description="Test",
754
+ messages=[
755
+ PromptMessage(
756
+ role="user",
757
+ content="{{^namespace}}All namespaces{{/namespace}}{{#namespace}}Namespace: {{namespace}}{{/namespace}}"
758
+ )
759
+ ]
760
+ )
761
+ result = render_prompt(prompt, {"namespace": "production"})
762
+ assert "All namespaces" not in result[0].content
763
+ assert "Namespace: production" in result[0].content
764
+
765
+ @pytest.mark.unit
766
+ def test_multiple_messages(self):
767
+ """Test rendering multiple messages."""
768
+ prompt = CustomPrompt(
769
+ name="test",
770
+ description="Test",
771
+ messages=[
772
+ PromptMessage(role="user", content="Hello {{name}}"),
773
+ PromptMessage(role="assistant", content="Hi there!"),
774
+ PromptMessage(role="user", content="Debug {{pod}}"),
775
+ ]
776
+ )
777
+ result = render_prompt(prompt, {"name": "Admin", "pod": "nginx"})
778
+ assert len(result) == 3
779
+ assert result[0].content == "Hello Admin"
780
+ assert result[2].content == "Debug nginx"
781
+
782
+ @pytest.mark.unit
783
+ def test_unsubstituted_variables_removed(self):
784
+ """Test that unsubstituted optional variables are removed."""
785
+ prompt = CustomPrompt(
786
+ name="test",
787
+ description="Test",
788
+ messages=[
789
+ PromptMessage(role="user", content="Check {{resource}} status {{unknown_var}}")
790
+ ]
791
+ )
792
+ result = render_prompt(prompt, {"resource": "pods"})
793
+ assert result[0].content == "Check pods status"
794
+
795
+
796
+ class TestLoadPromptsFromConfig:
797
+ """Tests for load_prompts_from_config function."""
798
+
799
+ @pytest.mark.unit
800
+ def test_load_simple_prompt(self):
801
+ """Test loading a simple prompt from config dict."""
802
+ config = {
803
+ "prompts": [
804
+ {
805
+ "name": "debug-pod",
806
+ "title": "Debug Pod",
807
+ "description": "Debug a pod",
808
+ "arguments": [
809
+ {"name": "pod_name", "required": True, "description": "Pod name"}
810
+ ],
811
+ "messages": [
812
+ {"role": "user", "content": "Debug {{pod_name}}"}
813
+ ]
814
+ }
815
+ ]
816
+ }
817
+ prompts = load_prompts_from_config(config)
818
+ assert len(prompts) == 1
819
+ assert prompts[0].name == "debug-pod"
820
+ assert prompts[0].title == "Debug Pod"
821
+ assert len(prompts[0].arguments) == 1
822
+ assert prompts[0].arguments[0].required is True
823
+
824
+ @pytest.mark.unit
825
+ def test_load_multiple_prompts(self):
826
+ """Test loading multiple prompts from config."""
827
+ config = {
828
+ "prompts": [
829
+ {"name": "prompt1", "description": "First", "messages": [{"role": "user", "content": "Hello"}]},
830
+ {"name": "prompt2", "description": "Second", "messages": [{"role": "user", "content": "World"}]},
831
+ ]
832
+ }
833
+ prompts = load_prompts_from_config(config)
834
+ assert len(prompts) == 2
835
+
836
+ @pytest.mark.unit
837
+ def test_load_empty_config(self):
838
+ """Test loading from empty config returns empty list."""
839
+ prompts = load_prompts_from_config({})
840
+ assert prompts == []
841
+
842
+ @pytest.mark.unit
843
+ def test_load_config_with_defaults(self):
844
+ """Test that argument defaults are loaded."""
845
+ config = {
846
+ "prompts": [
847
+ {
848
+ "name": "test",
849
+ "description": "Test",
850
+ "arguments": [
851
+ {"name": "namespace", "required": False, "default": "default"}
852
+ ],
853
+ "messages": [{"role": "user", "content": "Test"}]
854
+ }
855
+ ]
856
+ }
857
+ prompts = load_prompts_from_config(config)
858
+ assert prompts[0].arguments[0].default == "default"
859
+
860
+ @pytest.mark.unit
861
+ def test_skip_invalid_prompts(self):
862
+ """Test that invalid prompts are skipped."""
863
+ config = {
864
+ "prompts": [
865
+ {"name": "", "description": "Invalid - no name"}, # Invalid
866
+ {"name": "valid", "description": "Valid prompt", "messages": [{"role": "user", "content": "Hi"}]},
867
+ ]
868
+ }
869
+ prompts = load_prompts_from_config(config)
870
+ assert len(prompts) == 1
871
+ assert prompts[0].name == "valid"
872
+
873
+
874
+ class TestLoadPromptsFromTomlFile:
875
+ """Tests for load_prompts_from_toml_file function."""
876
+
877
+ @pytest.mark.unit
878
+ def test_load_from_valid_toml(self):
879
+ """Test loading prompts from a valid TOML file."""
880
+ toml_content = """
881
+ [[prompts]]
882
+ name = "test-prompt"
883
+ title = "Test Prompt"
884
+ description = "A test prompt for testing"
885
+
886
+ [[prompts.arguments]]
887
+ name = "target"
888
+ description = "Target resource"
889
+ required = true
890
+
891
+ [[prompts.messages]]
892
+ role = "user"
893
+ content = "Check {{target}}"
894
+ """
895
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.toml', delete=False) as f:
896
+ f.write(toml_content)
897
+ f.flush()
898
+ try:
899
+ prompts = load_prompts_from_toml_file(f.name)
900
+ assert len(prompts) == 1
901
+ assert prompts[0].name == "test-prompt"
902
+ assert len(prompts[0].arguments) == 1
903
+ finally:
904
+ os.unlink(f.name)
905
+
906
+ @pytest.mark.unit
907
+ def test_load_from_nonexistent_file(self):
908
+ """Test loading from nonexistent file returns empty list."""
909
+ prompts = load_prompts_from_toml_file("/nonexistent/path/prompts.toml")
910
+ assert prompts == []
911
+
912
+
913
+ class TestValidatePromptArgs:
914
+ """Tests for validate_prompt_args function."""
915
+
916
+ @pytest.mark.unit
917
+ def test_valid_required_args(self):
918
+ """Test validation passes with all required args."""
919
+ prompt = CustomPrompt(
920
+ name="test",
921
+ description="Test",
922
+ arguments=[
923
+ PromptArgument(name="required_arg", required=True),
924
+ ]
925
+ )
926
+ errors = validate_prompt_args(prompt, {"required_arg": "value"})
927
+ assert errors == []
928
+
929
+ @pytest.mark.unit
930
+ def test_missing_required_arg(self):
931
+ """Test validation fails with missing required arg."""
932
+ prompt = CustomPrompt(
933
+ name="test",
934
+ description="Test",
935
+ arguments=[
936
+ PromptArgument(name="required_arg", required=True),
937
+ ]
938
+ )
939
+ errors = validate_prompt_args(prompt, {})
940
+ assert len(errors) == 1
941
+ assert "required_arg" in errors[0]
942
+
943
+ @pytest.mark.unit
944
+ def test_empty_required_arg(self):
945
+ """Test validation fails with empty required arg."""
946
+ prompt = CustomPrompt(
947
+ name="test",
948
+ description="Test",
949
+ arguments=[
950
+ PromptArgument(name="required_arg", required=True),
951
+ ]
952
+ )
953
+ errors = validate_prompt_args(prompt, {"required_arg": ""})
954
+ assert len(errors) == 1
955
+
956
+ @pytest.mark.unit
957
+ def test_optional_args_not_required(self):
958
+ """Test validation passes without optional args."""
959
+ prompt = CustomPrompt(
960
+ name="test",
961
+ description="Test",
962
+ arguments=[
963
+ PromptArgument(name="optional_arg", required=False),
964
+ ]
965
+ )
966
+ errors = validate_prompt_args(prompt, {})
967
+ assert errors == []
968
+
969
+
970
+ class TestApplyDefaults:
971
+ """Tests for apply_defaults function."""
972
+
973
+ @pytest.mark.unit
974
+ def test_apply_missing_defaults(self):
975
+ """Test that defaults are applied for missing args."""
976
+ prompt = CustomPrompt(
977
+ name="test",
978
+ description="Test",
979
+ arguments=[
980
+ PromptArgument(name="namespace", default="default"),
981
+ PromptArgument(name="timeout", default="30"),
982
+ ]
983
+ )
984
+ result = apply_defaults(prompt, {})
985
+ assert result["namespace"] == "default"
986
+ assert result["timeout"] == "30"
987
+
988
+ @pytest.mark.unit
989
+ def test_preserve_provided_values(self):
990
+ """Test that provided values are preserved."""
991
+ prompt = CustomPrompt(
992
+ name="test",
993
+ description="Test",
994
+ arguments=[
995
+ PromptArgument(name="namespace", default="default"),
996
+ ]
997
+ )
998
+ result = apply_defaults(prompt, {"namespace": "production"})
999
+ assert result["namespace"] == "production"
1000
+
1001
+ @pytest.mark.unit
1002
+ def test_no_default_for_empty_string(self):
1003
+ """Test that empty string default is not applied."""
1004
+ prompt = CustomPrompt(
1005
+ name="test",
1006
+ description="Test",
1007
+ arguments=[
1008
+ PromptArgument(name="arg", default=""),
1009
+ ]
1010
+ )
1011
+ result = apply_defaults(prompt, {})
1012
+ assert "arg" not in result
1013
+
1014
+
1015
+ class TestGetPromptSchema:
1016
+ """Tests for get_prompt_schema function."""
1017
+
1018
+ @pytest.mark.unit
1019
+ def test_generate_schema(self):
1020
+ """Test generating JSON Schema for prompt."""
1021
+ prompt = CustomPrompt(
1022
+ name="test",
1023
+ description="Test",
1024
+ arguments=[
1025
+ PromptArgument(name="pod_name", description="Pod name", required=True),
1026
+ PromptArgument(name="namespace", description="Namespace", required=False, default="default"),
1027
+ ]
1028
+ )
1029
+ schema = get_prompt_schema(prompt)
1030
+
1031
+ assert schema["type"] == "object"
1032
+ assert "pod_name" in schema["properties"]
1033
+ assert "namespace" in schema["properties"]
1034
+ assert schema["properties"]["namespace"]["default"] == "default"
1035
+ assert "pod_name" in schema["required"]
1036
+ assert "namespace" not in schema["required"]
1037
+
1038
+
1039
+ # =============================================================================
1040
+ # Built-in Prompts Tests
1041
+ # =============================================================================
1042
+
1043
+
1044
+ class TestBuiltinPrompts:
1045
+ """Tests for built-in prompts."""
1046
+
1047
+ @pytest.mark.unit
1048
+ def test_all_builtin_prompts_exist(self):
1049
+ """Test that all expected built-in prompts exist."""
1050
+ assert len(BUILTIN_PROMPTS) == 6
1051
+ names = [p.name for p in BUILTIN_PROMPTS]
1052
+ assert "cluster-health-check" in names
1053
+ assert "debug-workload" in names
1054
+ assert "resource-usage" in names
1055
+ assert "security-posture" in names
1056
+ assert "deployment-checklist" in names
1057
+ assert "incident-response" in names
1058
+
1059
+ @pytest.mark.unit
1060
+ def test_get_builtin_prompts_returns_copy(self):
1061
+ """Test that get_builtin_prompts returns a copy."""
1062
+ prompts1 = get_builtin_prompts()
1063
+ prompts2 = get_builtin_prompts()
1064
+ assert prompts1 is not prompts2
1065
+ assert len(prompts1) == len(prompts2)
1066
+
1067
+ @pytest.mark.unit
1068
+ def test_get_builtin_prompt_by_name(self):
1069
+ """Test retrieving a built-in prompt by name."""
1070
+ prompt = get_builtin_prompt_by_name("cluster-health-check")
1071
+ assert prompt is not None
1072
+ assert prompt.name == "cluster-health-check"
1073
+ assert prompt.title == "Cluster Health Check"
1074
+
1075
+ @pytest.mark.unit
1076
+ def test_get_builtin_prompt_not_found(self):
1077
+ """Test retrieving a non-existent built-in prompt."""
1078
+ prompt = get_builtin_prompt_by_name("nonexistent")
1079
+ assert prompt is None
1080
+
1081
+
1082
+ class TestClusterHealthCheckPrompt:
1083
+ """Tests for CLUSTER_HEALTH_CHECK built-in prompt."""
1084
+
1085
+ @pytest.mark.unit
1086
+ def test_prompt_structure(self):
1087
+ """Test CLUSTER_HEALTH_CHECK prompt structure."""
1088
+ assert CLUSTER_HEALTH_CHECK.name == "cluster-health-check"
1089
+ assert CLUSTER_HEALTH_CHECK.title == "Cluster Health Check"
1090
+ assert len(CLUSTER_HEALTH_CHECK.arguments) == 3
1091
+ assert len(CLUSTER_HEALTH_CHECK.messages) == 1
1092
+
1093
+ @pytest.mark.unit
1094
+ def test_prompt_arguments(self):
1095
+ """Test CLUSTER_HEALTH_CHECK prompt arguments."""
1096
+ arg_names = [a.name for a in CLUSTER_HEALTH_CHECK.arguments]
1097
+ assert "namespace" in arg_names
1098
+ assert "check_events" in arg_names
1099
+ assert "check_metrics" in arg_names
1100
+
1101
+ @pytest.mark.unit
1102
+ def test_render_with_namespace(self):
1103
+ """Test rendering with namespace specified."""
1104
+ result = render_prompt(CLUSTER_HEALTH_CHECK, {"namespace": "production"})
1105
+ assert "in namespace production" in result[0].content
1106
+
1107
+ @pytest.mark.unit
1108
+ def test_render_without_namespace(self):
1109
+ """Test rendering without namespace shows cluster-wide."""
1110
+ result = render_prompt(CLUSTER_HEALTH_CHECK, {})
1111
+ # Should not have "in namespace" since namespace is empty
1112
+ assert result[0].content.count("in namespace ") == 0 or "namespace" not in result[0].content.split("in namespace ")[1].split()[0] if "in namespace " in result[0].content else True
1113
+
1114
+
1115
+ class TestDebugWorkloadPrompt:
1116
+ """Tests for DEBUG_WORKLOAD built-in prompt."""
1117
+
1118
+ @pytest.mark.unit
1119
+ def test_prompt_structure(self):
1120
+ """Test DEBUG_WORKLOAD prompt structure."""
1121
+ assert DEBUG_WORKLOAD.name == "debug-workload"
1122
+ assert DEBUG_WORKLOAD.title == "Debug Workload Issues"
1123
+ assert len(DEBUG_WORKLOAD.arguments) == 4
1124
+
1125
+ @pytest.mark.unit
1126
+ def test_required_arguments(self):
1127
+ """Test DEBUG_WORKLOAD has required workload_name argument."""
1128
+ workload_arg = next(a for a in DEBUG_WORKLOAD.arguments if a.name == "workload_name")
1129
+ assert workload_arg.required is True
1130
+
1131
+ @pytest.mark.unit
1132
+ def test_render_with_all_args(self):
1133
+ """Test rendering with all arguments."""
1134
+ result = render_prompt(DEBUG_WORKLOAD, {
1135
+ "workload_name": "nginx",
1136
+ "namespace": "production",
1137
+ "workload_type": "deployment",
1138
+ "include_related": "true"
1139
+ })
1140
+ assert "nginx" in result[0].content
1141
+ assert "production" in result[0].content
1142
+ assert "deployment" in result[0].content
1143
+
1144
+
1145
+ class TestResourceUsagePrompt:
1146
+ """Tests for RESOURCE_USAGE built-in prompt."""
1147
+
1148
+ @pytest.mark.unit
1149
+ def test_prompt_structure(self):
1150
+ """Test RESOURCE_USAGE prompt structure."""
1151
+ assert RESOURCE_USAGE.name == "resource-usage"
1152
+ assert len(RESOURCE_USAGE.arguments) == 4
1153
+
1154
+ @pytest.mark.unit
1155
+ def test_default_thresholds(self):
1156
+ """Test default threshold values."""
1157
+ cpu_arg = next(a for a in RESOURCE_USAGE.arguments if a.name == "threshold_cpu")
1158
+ mem_arg = next(a for a in RESOURCE_USAGE.arguments if a.name == "threshold_memory")
1159
+ assert cpu_arg.default == "80"
1160
+ assert mem_arg.default == "80"
1161
+
1162
+
1163
+ class TestSecurityPosturePrompt:
1164
+ """Tests for SECURITY_POSTURE built-in prompt."""
1165
+
1166
+ @pytest.mark.unit
1167
+ def test_prompt_structure(self):
1168
+ """Test SECURITY_POSTURE prompt structure."""
1169
+ assert SECURITY_POSTURE.name == "security-posture"
1170
+ assert len(SECURITY_POSTURE.arguments) == 4
1171
+
1172
+ @pytest.mark.unit
1173
+ def test_conditional_sections(self):
1174
+ """Test conditional sections for RBAC, network, secrets checks."""
1175
+ result = render_prompt(SECURITY_POSTURE, {
1176
+ "check_rbac": "true",
1177
+ "check_network": "false",
1178
+ "check_secrets": "true"
1179
+ })
1180
+ assert "RBAC Analysis" in result[0].content
1181
+ assert "Network Security" not in result[0].content
1182
+ assert "Secrets Management" in result[0].content
1183
+
1184
+
1185
+ class TestDeploymentChecklistPrompt:
1186
+ """Tests for DEPLOYMENT_CHECKLIST built-in prompt."""
1187
+
1188
+ @pytest.mark.unit
1189
+ def test_prompt_structure(self):
1190
+ """Test DEPLOYMENT_CHECKLIST prompt structure."""
1191
+ assert DEPLOYMENT_CHECKLIST.name == "deployment-checklist"
1192
+ assert len(DEPLOYMENT_CHECKLIST.arguments) == 4
1193
+
1194
+ @pytest.mark.unit
1195
+ def test_required_arguments(self):
1196
+ """Test required arguments."""
1197
+ required_args = [a.name for a in DEPLOYMENT_CHECKLIST.arguments if a.required]
1198
+ assert "app_name" in required_args
1199
+ assert "namespace" in required_args
1200
+ assert "image" in required_args
1201
+
1202
+ @pytest.mark.unit
1203
+ def test_render_with_args(self):
1204
+ """Test rendering with deployment args."""
1205
+ result = render_prompt(DEPLOYMENT_CHECKLIST, {
1206
+ "app_name": "myapp",
1207
+ "namespace": "production",
1208
+ "image": "myapp:v1.2.3",
1209
+ "replicas": "3"
1210
+ })
1211
+ assert "myapp" in result[0].content
1212
+ assert "production" in result[0].content
1213
+ assert "myapp:v1.2.3" in result[0].content
1214
+ assert "3" in result[0].content
1215
+
1216
+
1217
+ class TestIncidentResponsePrompt:
1218
+ """Tests for INCIDENT_RESPONSE built-in prompt."""
1219
+
1220
+ @pytest.mark.unit
1221
+ def test_prompt_structure(self):
1222
+ """Test INCIDENT_RESPONSE prompt structure."""
1223
+ assert INCIDENT_RESPONSE.name == "incident-response"
1224
+ assert len(INCIDENT_RESPONSE.arguments) == 4
1225
+
1226
+ @pytest.mark.unit
1227
+ def test_required_arguments(self):
1228
+ """Test required arguments."""
1229
+ required_args = [a.name for a in INCIDENT_RESPONSE.arguments if a.required]
1230
+ assert "incident_type" in required_args
1231
+ assert "affected_service" in required_args
1232
+ assert "namespace" in required_args
1233
+
1234
+ @pytest.mark.unit
1235
+ def test_default_severity(self):
1236
+ """Test default severity."""
1237
+ severity_arg = next(a for a in INCIDENT_RESPONSE.arguments if a.name == "severity")
1238
+ assert severity_arg.default == "high"
1239
+
1240
+ @pytest.mark.unit
1241
+ def test_render_incident(self):
1242
+ """Test rendering incident response."""
1243
+ result = render_prompt(INCIDENT_RESPONSE, {
1244
+ "incident_type": "pod-crash",
1245
+ "affected_service": "api-server",
1246
+ "namespace": "production",
1247
+ "severity": "critical"
1248
+ })
1249
+ assert "pod-crash" in result[0].content
1250
+ assert "api-server" in result[0].content
1251
+ assert "production" in result[0].content
1252
+ assert "critical" in result[0].content