codemie-test-harness 0.1.179__py3-none-any.whl → 0.1.180__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.

Potentially problematic release.


This version of codemie-test-harness might be problematic. Click here for more details.

Files changed (130) hide show
  1. codemie_test_harness/cli/cli.py +2 -0
  2. codemie_test_harness/cli/commands/marks_cmd.py +39 -0
  3. codemie_test_harness/cli/marks_utils.py +338 -0
  4. codemie_test_harness/cli/runner.py +40 -0
  5. codemie_test_harness/tests/assistant/datasource/test_code_datasource.py +3 -3
  6. codemie_test_harness/tests/assistant/datasource/test_confluence_datasource.py +3 -3
  7. codemie_test_harness/tests/assistant/datasource/test_file_indexing.py +5 -5
  8. codemie_test_harness/tests/assistant/datasource/test_google_datasource.py +3 -3
  9. codemie_test_harness/tests/assistant/datasource/test_jira_datasource.py +3 -3
  10. codemie_test_harness/tests/assistant/default_integrations/test_default_integrations_for_tool.py +8 -8
  11. codemie_test_harness/tests/assistant/default_integrations/test_default_integrations_for_tool_kit.py +8 -8
  12. codemie_test_harness/tests/assistant/default_integrations/test_default_integrations_for_tool_with_datasource.py +8 -8
  13. codemie_test_harness/tests/assistant/test_assistants.py +22 -22
  14. codemie_test_harness/tests/assistant/tools/access_management/test_keycloak_tool.py +1 -1
  15. codemie_test_harness/tests/assistant/tools/ado/test_assistant_for_ado_test_plan_tools.py +2 -2
  16. codemie_test_harness/tests/assistant/tools/ado/test_assistant_for_ado_wiki_tools.py +2 -2
  17. codemie_test_harness/tests/assistant/tools/ado/test_assistant_for_ado_work_item_tools.py +2 -2
  18. codemie_test_harness/tests/assistant/tools/cloud/test_cloud_tools.py +1 -1
  19. codemie_test_harness/tests/assistant/tools/codebase/test_codebase_tools.py +2 -2
  20. codemie_test_harness/tests/assistant/tools/datamanagement/test_assistant_with_data_management_tools.py +2 -2
  21. codemie_test_harness/tests/assistant/tools/filemanagement/test_assistant_with_file_management_tools.py +4 -4
  22. codemie_test_harness/tests/assistant/tools/git/test_assistant_with_git_tools.py +7 -7
  23. codemie_test_harness/tests/assistant/tools/mcp/test_cli_mcp_server.py +2 -2
  24. codemie_test_harness/tests/assistant/tools/mcp/test_mcp_servers.py +2 -2
  25. codemie_test_harness/tests/assistant/tools/notification/test_assistant_notification_tools.py +2 -2
  26. codemie_test_harness/tests/assistant/tools/openapi/test_assistant_with_open_api_tools.py +1 -1
  27. codemie_test_harness/tests/assistant/tools/plugin/test_assistant_with_development_plugin.py +2 -2
  28. codemie_test_harness/tests/assistant/tools/plugin/test_assistant_with_plugin_and_mcp_servers.py +2 -2
  29. codemie_test_harness/tests/assistant/tools/plugin/test_single_assistant_dual_time_plugins.py +1 -1
  30. codemie_test_harness/tests/assistant/tools/project_management/test_assistant_pm_tools.py +6 -6
  31. codemie_test_harness/tests/assistant/tools/report_portal/test_assistant_report_portal_tools.py +1 -1
  32. codemie_test_harness/tests/assistant/tools/research/test_assistant_research_tools.py +2 -2
  33. codemie_test_harness/tests/assistant/tools/servicenow/test_servicenow_tools.py +1 -1
  34. codemie_test_harness/tests/assistant/tools/vcs/test_assistant_with_vcs_tools.py +1 -1
  35. codemie_test_harness/tests/conversations/test_conversations_endpoints.py +5 -5
  36. codemie_test_harness/tests/e2e/test_e2e.py +6 -6
  37. codemie_test_harness/tests/integrations/project/test_default_integrations.py +14 -14
  38. codemie_test_harness/tests/integrations/project/test_project_integrations.py +9 -9
  39. codemie_test_harness/tests/integrations/user/test_default_integrations.py +14 -14
  40. codemie_test_harness/tests/integrations/user/test_user_integrations.py +12 -12
  41. codemie_test_harness/tests/llm/assistants/test_lite_llm.py +2 -2
  42. codemie_test_harness/tests/llm/assistants/test_llm.py +3 -3
  43. codemie_test_harness/tests/providers/test_providers_endpoints.py +11 -11
  44. codemie_test_harness/tests/search/test_search_assistant.py +1 -1
  45. codemie_test_harness/tests/search/test_search_datasource.py +5 -5
  46. codemie_test_harness/tests/search/test_search_integration.py +2 -2
  47. codemie_test_harness/tests/search/test_search_workflow.py +1 -1
  48. codemie_test_harness/tests/service/test_assistant_service.py +1 -1
  49. codemie_test_harness/tests/test_data/codebase_tools_test_data.py +11 -5
  50. codemie_test_harness/tests/ui/assistants/test_create_assistant.py +11 -11
  51. codemie_test_harness/tests/ui/datasource/test_create_datasource.py +7 -7
  52. codemie_test_harness/tests/ui/datasource/test_datasource_page.py +3 -3
  53. codemie_test_harness/tests/ui/datasource/test_edit_datasource.py +5 -5
  54. codemie_test_harness/tests/ui/datasource/test_view_datasource.py +5 -5
  55. codemie_test_harness/tests/ui/integrations/test_create_integration.py +9 -9
  56. codemie_test_harness/tests/ui/workflows/test_create_workflow.py +12 -12
  57. codemie_test_harness/tests/ui/workflows/test_edit_workflow.py +15 -15
  58. codemie_test_harness/tests/ui/workflows/test_workflow_details.py +19 -19
  59. codemie_test_harness/tests/ui/workflows/test_workflow_executions_page.py +36 -36
  60. codemie_test_harness/tests/ui/workflows/test_workflow_templates.py +8 -8
  61. codemie_test_harness/tests/ui/workflows/test_workflows.py +8 -8
  62. codemie_test_harness/tests/workflow/assistant_tools/access_management/test_workflow_with_assistant_with_keycloak_tool.py +1 -1
  63. codemie_test_harness/tests/workflow/assistant_tools/ado/test_workflow_with_assistant_with_ado_test_plan_tools.py +2 -2
  64. codemie_test_harness/tests/workflow/assistant_tools/ado/test_workflow_with_assistant_with_ado_wiki_tools.py +2 -2
  65. codemie_test_harness/tests/workflow/assistant_tools/ado/test_workflow_with_assistant_with_ado_work_item_tools.py +2 -2
  66. codemie_test_harness/tests/workflow/assistant_tools/cloud/test_workflow_with_assistant_cloud_tools.py +1 -1
  67. codemie_test_harness/tests/workflow/assistant_tools/codebase/test_worfklow_with_assistant_codebase_tools.py +2 -2
  68. codemie_test_harness/tests/workflow/assistant_tools/data_management/test_workflow_with_assistant_with_data_management_tools.py +2 -2
  69. codemie_test_harness/tests/workflow/assistant_tools/default_integrations/test_default_integrations_for_tool.py +8 -8
  70. codemie_test_harness/tests/workflow/assistant_tools/default_integrations/test_default_integrations_for_tool_kit.py +8 -8
  71. codemie_test_harness/tests/workflow/assistant_tools/default_integrations/test_default_integrations_for_tool_with_datasource.py +8 -8
  72. codemie_test_harness/tests/workflow/assistant_tools/file_management/test_workflow_with_assistant_with_file_management_tools.py +5 -5
  73. codemie_test_harness/tests/workflow/assistant_tools/git/test_workflow_with_assistant_git_tools.py +7 -7
  74. codemie_test_harness/tests/workflow/assistant_tools/mcp/test_workflow_with_assistant_with_mcp_server.py +3 -3
  75. codemie_test_harness/tests/workflow/assistant_tools/notification/test_workflow_with_assistant_notification_tools.py +2 -2
  76. codemie_test_harness/tests/workflow/assistant_tools/open_api/test_workflow_with_assistant_with_open_api_tools.py +1 -1
  77. codemie_test_harness/tests/workflow/assistant_tools/plugin/test_workflow_with_assistant_with_development_plugin.py +2 -2
  78. codemie_test_harness/tests/workflow/assistant_tools/plugin/test_workflow_with_assistant_with_plugin_and_mcp_servers.py +2 -2
  79. codemie_test_harness/tests/workflow/assistant_tools/project_management/test_workflow_with_assistant_pm_tools.py +1 -1
  80. codemie_test_harness/tests/workflow/assistant_tools/report_portal/test_workflow_with_assistant_with_report_portal_tools.py +1 -1
  81. codemie_test_harness/tests/workflow/assistant_tools/research/test_workflow_with_assistant_research_tools.py +2 -2
  82. codemie_test_harness/tests/workflow/assistant_tools/servicenow/test_workflow_with_servicenow_tools.py +1 -1
  83. codemie_test_harness/tests/workflow/assistant_tools/vcs/workflow_with_assistant_vcs_tools.py +1 -1
  84. codemie_test_harness/tests/workflow/config_validation/test_config_validation.py +1 -1
  85. codemie_test_harness/tests/workflow/direct_tools_calling/default_integrations/test_default_integrations_for_tool.py +8 -8
  86. codemie_test_harness/tests/workflow/direct_tools_calling/default_integrations/test_default_integrations_for_tool_kit.py +8 -8
  87. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_access_management_tool.py +3 -3
  88. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_ado_test_plan_tools.py +3 -3
  89. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_ado_wiki_tools.py +3 -3
  90. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_ado_work_item_tools.py +3 -3
  91. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_cloud_tools.py +3 -3
  92. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_codebase_tools.py +3 -3
  93. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_data_management_tools_elastic.py +3 -3
  94. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_data_management_tools_sql.py +3 -3
  95. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_file_management_tools.py +3 -3
  96. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_notification_tools.py +3 -3
  97. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_open_api_tools.py +3 -3
  98. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_plugin_tools.py +4 -4
  99. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_project_management_tools.py +3 -3
  100. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_report_portal_tools.py +3 -3
  101. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_research_tools.py +3 -3
  102. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_servicenow_tools.py +3 -3
  103. codemie_test_harness/tests/workflow/direct_tools_calling/test_workflow_with_vcs_tools.py +3 -3
  104. codemie_test_harness/tests/workflow/test_workflows.py +1 -1
  105. codemie_test_harness/tests/workflow/virtual_assistant_tools/access_management/test_workflow_with_keycloak_tool.py +1 -1
  106. codemie_test_harness/tests/workflow/virtual_assistant_tools/ado/test_workflow_with_ado_test_plan_tools.py +2 -2
  107. codemie_test_harness/tests/workflow/virtual_assistant_tools/ado/test_workflow_with_ado_wiki_tools.py +2 -2
  108. codemie_test_harness/tests/workflow/virtual_assistant_tools/ado/test_workflow_with_ado_work_item_tools.py +2 -2
  109. codemie_test_harness/tests/workflow/virtual_assistant_tools/cloud/test_workflow_with_cloud_tools.py +4 -4
  110. codemie_test_harness/tests/workflow/virtual_assistant_tools/codebase/test_workflow_with_codebase_tools.py +2 -2
  111. codemie_test_harness/tests/workflow/virtual_assistant_tools/data_management/test_workflow_with_data_management_tools.py +2 -2
  112. codemie_test_harness/tests/workflow/virtual_assistant_tools/default_integrations/test_default_integrations_for_tool.py +8 -8
  113. codemie_test_harness/tests/workflow/virtual_assistant_tools/default_integrations/test_default_integrations_for_tool_kit.py +8 -8
  114. codemie_test_harness/tests/workflow/virtual_assistant_tools/default_integrations/test_default_integrations_for_tool_with_datasource.py +8 -8
  115. codemie_test_harness/tests/workflow/virtual_assistant_tools/file_management/test_workflow_with_file_management_tools.py +5 -5
  116. codemie_test_harness/tests/workflow/virtual_assistant_tools/git/test_workflow_with_git_tools.py +7 -7
  117. codemie_test_harness/tests/workflow/virtual_assistant_tools/mcp/test_workflow_with_mcp_server.py +3 -3
  118. codemie_test_harness/tests/workflow/virtual_assistant_tools/notification/test_workflow_with_notification_tools.py +4 -4
  119. codemie_test_harness/tests/workflow/virtual_assistant_tools/open_api/test_workflow_with_open_api_tools.py +2 -2
  120. codemie_test_harness/tests/workflow/virtual_assistant_tools/plugin/test_workflow_with_development_plugin.py +2 -2
  121. codemie_test_harness/tests/workflow/virtual_assistant_tools/plugin/test_workflow_with_plugin_and_mcp_servers.py +2 -2
  122. codemie_test_harness/tests/workflow/virtual_assistant_tools/project_management/test_workflow_with_project_management_tools.py +2 -2
  123. codemie_test_harness/tests/workflow/virtual_assistant_tools/report_portal/test_workflow_with_report_portal_tool.py +1 -1
  124. codemie_test_harness/tests/workflow/virtual_assistant_tools/research/test_workflow_with_research_tools.py +2 -2
  125. codemie_test_harness/tests/workflow/virtual_assistant_tools/servicenow/test_workflow_with_servicenow_tools.py +1 -1
  126. codemie_test_harness/tests/workflow/virtual_assistant_tools/vcs/test_workflow_with_vcs_tools.py +1 -1
  127. {codemie_test_harness-0.1.179.dist-info → codemie_test_harness-0.1.180.dist-info}/METADATA +8 -8
  128. {codemie_test_harness-0.1.179.dist-info → codemie_test_harness-0.1.180.dist-info}/RECORD +130 -128
  129. {codemie_test_harness-0.1.179.dist-info → codemie_test_harness-0.1.180.dist-info}/WHEEL +0 -0
  130. {codemie_test_harness-0.1.179.dist-info → codemie_test_harness-0.1.180.dist-info}/entry_points.txt +0 -0
@@ -35,6 +35,7 @@ from .commands.config_cmd import config_cmd
35
35
  from .commands.run_cmd import run_cmd
36
36
  from .commands.assistant_cmd import assistant_cmd
37
37
  from .commands.workflow_cmd import workflow_cmd
38
+ from .commands.marks_cmd import marks_cmd
38
39
 
39
40
 
40
41
  @click.group(context_settings=CONTEXT_SETTINGS)
@@ -130,6 +131,7 @@ cli.add_command(config_cmd)
130
131
  cli.add_command(run_cmd)
131
132
  cli.add_command(assistant_cmd)
132
133
  cli.add_command(workflow_cmd)
134
+ cli.add_command(marks_cmd)
133
135
 
134
136
 
135
137
  if __name__ == "__main__": # pragma: no cover
@@ -0,0 +1,39 @@
1
+ """CLI command for listing pytest marks."""
2
+
3
+ from __future__ import annotations
4
+ import click
5
+ from ..marks_utils import discover_all_marks, print_marks_list
6
+ from ..constants import CONSOLE
7
+
8
+
9
+ @click.command(name="marks")
10
+ @click.option(
11
+ "--verbose",
12
+ "-v",
13
+ is_flag=True,
14
+ help="Show detailed information in table format with line numbers",
15
+ )
16
+ @click.option(
17
+ "--count", "-c", is_flag=True, help="Show only the count of available marks"
18
+ )
19
+ def marks_cmd(verbose: bool, count: bool):
20
+ """List all available pytest marks in the test suite.
21
+
22
+ Examples:
23
+ codemie-test-harness marks # List all marks
24
+ codemie-test-harness marks --verbose # List marks with file details in table format
25
+ codemie-test-harness marks --count # Show only count
26
+ """
27
+ try:
28
+ if count:
29
+ # For count mode, we don't need detailed information
30
+ marks_info = discover_all_marks(include_details=False)
31
+ CONSOLE.print(f"[green]{len(marks_info)}[/green] pytest marks available")
32
+ return
33
+
34
+ # Let print_marks_list handle the discovery with appropriate details
35
+ print_marks_list(marks_info=None, show_files=verbose)
36
+
37
+ except Exception as e:
38
+ CONSOLE.print(f"[red]Error discovering marks: {str(e)}[/red]")
39
+ raise click.ClickException("Failed to discover pytest marks")
@@ -0,0 +1,338 @@
1
+ """Utilities for handling pytest marks in the test harness."""
2
+
3
+ import re
4
+ import ast
5
+ from pathlib import Path
6
+ from typing import Set, List, Dict
7
+ from .constants import CONSOLE
8
+ from rich.table import Table
9
+
10
+ try:
11
+ from importlib.resources import files as pkg_files
12
+ except Exception:
13
+ pkg_files = None
14
+
15
+
16
+ def extract_marks_from_file(
17
+ file_path: Path, include_lines: bool = False
18
+ ) -> Set[str] | Dict[str, List[int]]:
19
+ """Extract pytest marks from a Python test file.
20
+
21
+ Args:
22
+ file_path: Path to the Python file to analyze
23
+ include_lines: If True, return dict with mark names and line numbers
24
+
25
+ Returns:
26
+ Set of mark names found in the file, or dict mapping marks to line numbers
27
+ """
28
+ if include_lines:
29
+ marks_with_lines = {}
30
+ else:
31
+ marks = set()
32
+
33
+ try:
34
+ with open(file_path, "r", encoding="utf-8") as f:
35
+ content = f.read()
36
+
37
+ # Parse the file to AST for reliable extraction
38
+ try:
39
+ tree = ast.parse(content)
40
+ for node in ast.walk(tree):
41
+ if isinstance(node, ast.FunctionDef):
42
+ for decorator in node.decorator_list:
43
+ mark_name = _extract_mark_from_decorator(decorator)
44
+ if mark_name:
45
+ if include_lines:
46
+ if mark_name not in marks_with_lines:
47
+ marks_with_lines[mark_name] = []
48
+ marks_with_lines[mark_name].append(decorator.lineno)
49
+ else:
50
+ marks.add(mark_name)
51
+ except SyntaxError:
52
+ # If AST parsing fails, fall back to regex
53
+ pass
54
+
55
+ # Additional regex-based extraction for edge cases
56
+ if include_lines:
57
+ regex_marks_with_lines = _extract_marks_with_regex_and_lines(content)
58
+ for mark, lines in regex_marks_with_lines.items():
59
+ if mark not in marks_with_lines:
60
+ marks_with_lines[mark] = []
61
+ marks_with_lines[mark].extend(lines)
62
+ else:
63
+ regex_marks = _extract_marks_with_regex(content)
64
+ marks.update(regex_marks)
65
+
66
+ except (IOError, UnicodeDecodeError):
67
+ # Skip files that can't be read
68
+ pass
69
+
70
+ return marks_with_lines if include_lines else marks
71
+
72
+
73
+ def _extract_mark_from_decorator(decorator) -> str | None:
74
+ """Extract mark name from AST decorator node."""
75
+ mark_name = None
76
+
77
+ if isinstance(decorator, ast.Attribute):
78
+ # @pytest.mark.some_mark
79
+ if (
80
+ isinstance(decorator.value, ast.Attribute)
81
+ and isinstance(decorator.value.value, ast.Name)
82
+ and decorator.value.value.id == "pytest"
83
+ and decorator.value.attr == "mark"
84
+ ):
85
+ mark_name = decorator.attr
86
+ elif isinstance(decorator, ast.Call):
87
+ # @pytest.mark.some_mark(...) with arguments
88
+ if (
89
+ isinstance(decorator.func, ast.Attribute)
90
+ and isinstance(decorator.func.value, ast.Attribute)
91
+ and isinstance(decorator.func.value.value, ast.Name)
92
+ and decorator.func.value.value.id == "pytest"
93
+ and decorator.func.value.attr == "mark"
94
+ ):
95
+ mark_name = decorator.func.attr
96
+
97
+ # Filter out pytest built-in decorators that aren't marks
98
+ if mark_name in {
99
+ "parametrize",
100
+ "fixture",
101
+ "skip",
102
+ "skipif",
103
+ "xfail",
104
+ "usefixtures",
105
+ "testcase",
106
+ "todo",
107
+ }:
108
+ return None
109
+
110
+ return mark_name
111
+
112
+
113
+ def _extract_marks_with_regex(content: str) -> Set[str]:
114
+ """Extract marks using regex as fallback."""
115
+ marks = set()
116
+
117
+ # Pattern to match @pytest.mark.mark_name
118
+ pattern = r"@pytest\.mark\.(\w+)"
119
+ matches = re.findall(pattern, content)
120
+
121
+ for match in matches:
122
+ # Filter out pytest built-in decorators that aren't marks
123
+ if match not in {
124
+ "parametrize",
125
+ "fixture",
126
+ "skip",
127
+ "skipif",
128
+ "xfail",
129
+ "usefixtures",
130
+ "todo",
131
+ }:
132
+ marks.add(match)
133
+
134
+ return marks
135
+
136
+
137
+ def _extract_marks_with_regex_and_lines(content: str) -> Dict[str, List[int]]:
138
+ """Extract marks with line numbers using regex as fallback."""
139
+ marks_with_lines = {}
140
+ lines = content.split("\n")
141
+
142
+ # Pattern to match @pytest.mark.mark_name
143
+ pattern = r"@pytest\.mark\.(\w+)"
144
+
145
+ for line_num, line in enumerate(lines, 1):
146
+ matches = re.findall(pattern, line)
147
+ for match in matches:
148
+ # Filter out pytest built-in decorators that aren't marks
149
+ if match not in {
150
+ "parametrize",
151
+ "fixture",
152
+ "skip",
153
+ "skipif",
154
+ "xfail",
155
+ "usefixtures",
156
+ "todo",
157
+ }:
158
+ if match not in marks_with_lines:
159
+ marks_with_lines[match] = []
160
+ marks_with_lines[match].append(line_num)
161
+
162
+ return marks_with_lines
163
+
164
+
165
+ def _resolve_tests_path() -> str:
166
+ """Resolve tests path for mark discovery.
167
+
168
+ Returns:
169
+ Path to tests directory
170
+ """
171
+ # 1) Use importlib.resources to locate installed package "tests"
172
+ if pkg_files is not None:
173
+ try:
174
+ # Try to locate codemie_test_harness.tests package
175
+ tests_dir = Path(str(pkg_files("codemie_test_harness.tests")))
176
+ return str(tests_dir)
177
+ except Exception:
178
+ pass
179
+
180
+ # 2) Fallback to repo layout when running from source
181
+ # marks_utils.py -> cli -> codemie_test_harness -> <repo_root>
182
+ codemie_test_harness_root = (
183
+ Path(__file__).resolve().parents[1]
184
+ ) # codemie_test_harness directory
185
+ tests_path = str(codemie_test_harness_root / "tests")
186
+ return tests_path
187
+
188
+
189
+ def discover_all_marks(
190
+ include_details: bool = False,
191
+ ) -> Dict[str, List[str]] | Dict[str, List[Dict]]:
192
+ """Discover all pytest marks used in the test suite.
193
+
194
+ Args:
195
+ include_details: If True, return detailed info with line numbers
196
+
197
+ Returns:
198
+ Dictionary with mark names as keys and list of files (or detailed info) as values
199
+ """
200
+ tests_path = _resolve_tests_path()
201
+ tests_dir = Path(tests_path)
202
+
203
+ if not tests_dir.exists():
204
+ return {}
205
+
206
+ marks_info = {}
207
+
208
+ # Find all Python test files
209
+ for py_file in tests_dir.rglob("*.py"):
210
+ if py_file.name.startswith("test_") or py_file.name.endswith("_test.py"):
211
+ rel_path = str(py_file.relative_to(tests_dir))
212
+
213
+ if include_details:
214
+ marks_with_lines = extract_marks_from_file(py_file, include_lines=True)
215
+
216
+ for mark, lines in marks_with_lines.items():
217
+ if mark not in marks_info:
218
+ marks_info[mark] = []
219
+
220
+ for line in lines:
221
+ marks_info[mark].append({"file": rel_path, "line": line})
222
+ else:
223
+ marks = extract_marks_from_file(py_file)
224
+
225
+ for mark in marks:
226
+ if mark not in marks_info:
227
+ marks_info[mark] = []
228
+ # Store relative path for readability
229
+ if rel_path not in marks_info[mark]:
230
+ marks_info[mark].append(rel_path)
231
+
232
+ return marks_info
233
+
234
+
235
+ def get_all_available_marks() -> List[str]:
236
+ """Get sorted list of all available pytest marks."""
237
+ marks_to_files = discover_all_marks()
238
+ return sorted(marks_to_files.keys())
239
+
240
+
241
+ def is_valid_mark_expression(
242
+ expression: str, available_marks: List[str]
243
+ ) -> tuple[bool, List[str]]:
244
+ """Validate if a mark expression contains only known marks.
245
+
246
+ Args:
247
+ expression: The pytest mark expression (e.g., "smoke and ui", "not integration")
248
+ available_marks: List of all available marks
249
+
250
+ Returns:
251
+ Tuple of (is_valid, list_of_unknown_marks)
252
+ """
253
+ if not expression or not expression.strip():
254
+ return True, []
255
+
256
+ # Extract mark names from the expression
257
+ # This regex finds word tokens that could be mark names
258
+ potential_marks = re.findall(r"\b(\w+)\b", expression)
259
+
260
+ # Filter out logical operators and known keywords
261
+ logical_keywords = {"and", "or", "not", "true", "false"}
262
+ unknown_marks = []
263
+
264
+ available_marks_set = set(available_marks)
265
+
266
+ for mark in potential_marks:
267
+ if mark not in logical_keywords and mark not in available_marks_set:
268
+ unknown_marks.append(mark)
269
+
270
+ return len(unknown_marks) == 0, unknown_marks
271
+
272
+
273
+ def print_marks_list(marks_info=None, show_files: bool = False):
274
+ """Print formatted list of available marks.
275
+
276
+ Args:
277
+ marks_info: Optional pre-computed marks dictionary
278
+ show_files: Whether to show detailed information in table format
279
+ """
280
+ if marks_info is None:
281
+ marks_info = discover_all_marks(include_details=show_files)
282
+
283
+ if not marks_info:
284
+ CONSOLE.print("[yellow]No pytest marks found in test files.[/yellow]")
285
+ return
286
+
287
+ if show_files:
288
+ # Verbose mode - show table with detailed information
289
+ CONSOLE.print(
290
+ f"\n[bold cyan]Available pytest marks ({len(marks_info)} total):[/bold cyan]\n"
291
+ )
292
+
293
+ table = Table(show_header=True, header_style="bold magenta")
294
+ table.add_column("Mark", style="bold green", width=25)
295
+ table.add_column("File", style="cyan", width=110)
296
+ table.add_column("Usage Count", style="blue", justify="right", width=12)
297
+
298
+ for mark in sorted(marks_info.keys()):
299
+ details = marks_info[mark]
300
+
301
+ # Group by file and count occurrences
302
+ file_counts = {}
303
+ for detail in details:
304
+ file_path = detail["file"]
305
+ if file_path not in file_counts:
306
+ file_counts[file_path] = []
307
+ file_counts[file_path].append(detail["line"])
308
+
309
+ # Add rows for each file
310
+ first_row = True
311
+ for file_path, lines in sorted(file_counts.items()):
312
+ mark_display = (
313
+ mark if first_row else ""
314
+ ) # Just the mark name, no @pytest.mark. prefix
315
+ usage_count = str(len(lines))
316
+
317
+ table.add_row(mark_display, file_path, usage_count)
318
+ first_row = False
319
+
320
+ # Add separator row if not the last mark
321
+ if mark != sorted(marks_info.keys())[-1]:
322
+ table.add_row("", "", "", style="dim")
323
+
324
+ CONSOLE.print(table)
325
+ CONSOLE.print(f"\n[dim]Total: {len(marks_info)} unique marks found[/dim]")
326
+ else:
327
+ # Simple mode - just list marks
328
+ CONSOLE.print(
329
+ f"\n[bold cyan]Available pytest marks ({len(marks_info)} total):[/bold cyan]\n"
330
+ )
331
+
332
+ for mark in sorted(marks_info.keys()):
333
+ files = marks_info[mark]
334
+ CONSOLE.print(f"[green]• {mark}[/green] [dim]({len(files)} files)[/dim]")
335
+
336
+ CONSOLE.print(
337
+ "\n[dim]Use --verbose to see detailed information in table format.[/dim]"
338
+ )
@@ -10,6 +10,11 @@ except Exception:
10
10
  pkg_files = None
11
11
 
12
12
  from .constants import CONSOLE
13
+ from .marks_utils import (
14
+ get_all_available_marks,
15
+ is_valid_mark_expression,
16
+ print_marks_list,
17
+ )
13
18
 
14
19
 
15
20
  def resolve_tests_path_and_root() -> tuple[str, str]:
@@ -59,9 +64,44 @@ def build_pytest_cmd(
59
64
  return cmd, root_dir
60
65
 
61
66
 
67
+ def validate_marks_expression(marks: str) -> None:
68
+ """Validate that the marks expression contains only known marks.
69
+
70
+ Args:
71
+ marks: The pytest marks expression to validate
72
+
73
+ Raises:
74
+ SystemExit: If unknown marks are found
75
+ """
76
+ if not marks or not marks.strip():
77
+ return
78
+
79
+ try:
80
+ available_marks = get_all_available_marks()
81
+ is_valid, unknown_marks = is_valid_mark_expression(marks, available_marks)
82
+
83
+ if not is_valid:
84
+ CONSOLE.print(
85
+ f"[red]Error: Unknown pytest mark(s) found: {', '.join(unknown_marks)}[/red]"
86
+ )
87
+ CONSOLE.print(f"\n[yellow]Expression used:[/yellow] {marks}")
88
+ print_marks_list(show_files=False)
89
+ CONSOLE.print(
90
+ "\n[dim]Use 'codemie-test-harness marks' to see all available marks[/dim]"
91
+ )
92
+ raise SystemExit(1)
93
+
94
+ except Exception as e:
95
+ # If mark validation fails for any reason, issue a warning but continue
96
+ CONSOLE.print(f"[yellow]Warning: Could not validate marks: {str(e)}[/yellow]")
97
+
98
+
62
99
  def run_pytest(
63
100
  workers: int, marks: str, reruns: int, extra: Iterable[str] | None = None
64
101
  ) -> None:
102
+ # Validate marks before running pytest
103
+ validate_marks_expression(marks)
104
+
65
105
  cmd, root_dir = build_pytest_cmd(workers, marks, reruns, extra)
66
106
  CONSOLE.print(f"[cyan]Running:[/] {' '.join(cmd)} (cwd={root_dir})")
67
107
  raise SystemExit(subprocess.call(cmd, cwd=root_dir))
@@ -10,7 +10,7 @@ from codemie_test_harness.tests.utils.base_utils import get_random_name, assert_
10
10
 
11
11
  @pytest.mark.datasource
12
12
  @pytest.mark.gitlab
13
- @pytest.mark.regression
13
+ @pytest.mark.api
14
14
  @pytest.mark.parametrize(
15
15
  "embedding_model",
16
16
  index_test_data,
@@ -50,7 +50,7 @@ def test_create_index_application_with_embedding_model(
50
50
 
51
51
  @pytest.mark.datasource
52
52
  @pytest.mark.gitlab
53
- @pytest.mark.regression
53
+ @pytest.mark.api
54
54
  def test_edit_description_for_code_data_source(
55
55
  client,
56
56
  gitlab_datasource,
@@ -68,7 +68,7 @@ def test_edit_description_for_code_data_source(
68
68
 
69
69
  @pytest.mark.datasource
70
70
  @pytest.mark.gitlab
71
- @pytest.mark.regression
71
+ @pytest.mark.api
72
72
  def test_create_code_datasource_with_existing_name(gitlab_datasource, datasource_utils):
73
73
  datasource = datasource_utils.get_datasource(gitlab_datasource.id)
74
74
 
@@ -29,7 +29,7 @@ def confluence_cloud_datasource(datasource_utils, confluence_cloud_integration):
29
29
 
30
30
  @pytest.mark.datasource
31
31
  @pytest.mark.project_management
32
- @pytest.mark.regression
32
+ @pytest.mark.api
33
33
  @pytest.mark.parametrize(
34
34
  "datasource_fixture, prompt, expected_response",
35
35
  [
@@ -76,7 +76,7 @@ def test_create_datasource_with_confluence_and_confluence_cloud_integration(
76
76
 
77
77
  @pytest.mark.datasource
78
78
  @pytest.mark.confluence
79
- @pytest.mark.regression
79
+ @pytest.mark.api
80
80
  def test_edit_description_for_confluence_data_source(
81
81
  client, confluence_datasource, datasource_utils
82
82
  ):
@@ -92,7 +92,7 @@ def test_edit_description_for_confluence_data_source(
92
92
 
93
93
  @pytest.mark.datasource
94
94
  @pytest.mark.confluence
95
- @pytest.mark.regression
95
+ @pytest.mark.api
96
96
  def test_create_confluence_datasource_with_existing_name(
97
97
  datasource_utils, confluence_datasource
98
98
  ):
@@ -60,7 +60,7 @@ def pytest_generate_tests(metafunc):
60
60
 
61
61
  @pytest.mark.datasource
62
62
  @pytest.mark.file
63
- @pytest.mark.regression
63
+ @pytest.mark.api
64
64
  @pytest.mark.smoke
65
65
  def test_create_assistant_with_file_datasource(
66
66
  assistant,
@@ -91,7 +91,7 @@ def test_create_assistant_with_file_datasource(
91
91
 
92
92
  @pytest.mark.datasource
93
93
  @pytest.mark.file
94
- @pytest.mark.regression
94
+ @pytest.mark.api
95
95
  @pytest.mark.smoke
96
96
  def test_edit_description_for_file_datasource(datasource_utils):
97
97
  initial_description = "[Autotest] Initial CSV datasource description"
@@ -115,7 +115,7 @@ def test_edit_description_for_file_datasource(datasource_utils):
115
115
 
116
116
  @pytest.mark.datasource
117
117
  @pytest.mark.file
118
- @pytest.mark.regression
118
+ @pytest.mark.api
119
119
  @pytest.mark.smoke
120
120
  @pytest.mark.parametrize("file_name", large_files_test_data)
121
121
  def test_create_file_datasource_with_large_files(datasource_utils, file_name):
@@ -134,7 +134,7 @@ def test_create_file_datasource_with_large_files(datasource_utils, file_name):
134
134
 
135
135
  @pytest.mark.datasource
136
136
  @pytest.mark.file
137
- @pytest.mark.regression
137
+ @pytest.mark.api
138
138
  @pytest.mark.smoke
139
139
  def test_create_file_datasource_with_big_number_of_files(datasource_utils):
140
140
  files = [str(FILES_PATH / "test.txt") for _ in range(11)]
@@ -152,7 +152,7 @@ def test_create_file_datasource_with_big_number_of_files(datasource_utils):
152
152
 
153
153
  @pytest.mark.datasource
154
154
  @pytest.mark.file
155
- @pytest.mark.regression
155
+ @pytest.mark.api
156
156
  @pytest.mark.smoke
157
157
  def test_create_file_datasource_with_two_files(
158
158
  assistant, assistant_utils, datasource_utils, kb_context, similarity_check
@@ -15,7 +15,7 @@ from codemie_test_harness.tests.utils.base_utils import get_random_name, assert_
15
15
  @pytest.mark.datasource
16
16
  @pytest.mark.google
17
17
  @pytest.mark.smoke
18
- @pytest.mark.regression
18
+ @pytest.mark.api
19
19
  def test_create_datasource_with_google_integration(
20
20
  datasource_utils,
21
21
  assistant,
@@ -39,7 +39,7 @@ def test_create_datasource_with_google_integration(
39
39
 
40
40
  @pytest.mark.datasource
41
41
  @pytest.mark.google
42
- @pytest.mark.regression
42
+ @pytest.mark.api
43
43
  @pytest.mark.smoke
44
44
  def test_edit_description_for_google_data_source(
45
45
  client, datasource_utils, google_doc_datasource
@@ -56,7 +56,7 @@ def test_edit_description_for_google_data_source(
56
56
 
57
57
  @pytest.mark.datasource
58
58
  @pytest.mark.google
59
- @pytest.mark.regression
59
+ @pytest.mark.api
60
60
  @pytest.mark.smoke
61
61
  def test_create_google_doc_datasource_with_existing_name(
62
62
  google_doc_datasource, datasource_utils
@@ -27,7 +27,7 @@ def jira_cloud_datasource(datasource_utils, jira_cloud_integration):
27
27
 
28
28
  @pytest.mark.datasource
29
29
  @pytest.mark.project_management
30
- @pytest.mark.regression
30
+ @pytest.mark.api
31
31
  @pytest.mark.parametrize(
32
32
  "datasource_fixture, prompt, expected_response",
33
33
  [
@@ -74,7 +74,7 @@ def test_create_datasource_with_jira_and_jira_cloud_integration(
74
74
 
75
75
  @pytest.mark.datasource
76
76
  @pytest.mark.jira
77
- @pytest.mark.regression
77
+ @pytest.mark.api
78
78
  def test_edit_description_for_jira_data_source(
79
79
  client, datasource_utils, jira_datasource
80
80
  ):
@@ -90,7 +90,7 @@ def test_edit_description_for_jira_data_source(
90
90
 
91
91
  @pytest.mark.datasource
92
92
  @pytest.mark.jira
93
- @pytest.mark.regression
93
+ @pytest.mark.api
94
94
  def test_create_jira_datasource_with_existing_name(datasource_utils, jira_datasource):
95
95
  datasource = datasource_utils.get_datasource(jira_datasource.id)
96
96
 
@@ -18,8 +18,8 @@ from codemie_test_harness.tests.utils.constants import test_project_name
18
18
  @pytest.mark.integration
19
19
  @pytest.mark.default_integration
20
20
  @pytest.mark.not_for_parallel_run
21
- @pytest.mark.regression
22
- @pytest.mark.tescase("EPMCDME-6708")
21
+ @pytest.mark.api
22
+ @pytest.mark.testcase("EPMCDME-6708")
23
23
  @pytest.mark.parametrize(
24
24
  "user_integration, is_global, project_integration",
25
25
  integrations,
@@ -79,8 +79,8 @@ def test_assistant_should_use_user_integration_by_default(
79
79
  @pytest.mark.integration
80
80
  @pytest.mark.default_integration
81
81
  @pytest.mark.not_for_parallel_run
82
- @pytest.mark.regression
83
- @pytest.mark.tescase("EPMCDME-6708")
82
+ @pytest.mark.api
83
+ @pytest.mark.testcase("EPMCDME-6708")
84
84
  def test_assistant_with_global_and_project_integration(
85
85
  integration_utils,
86
86
  assistant,
@@ -123,8 +123,8 @@ def test_assistant_with_global_and_project_integration(
123
123
  @pytest.mark.integration
124
124
  @pytest.mark.default_integration
125
125
  @pytest.mark.not_for_parallel_run
126
- @pytest.mark.regression
127
- @pytest.mark.tescase("EPMCDME-6708")
126
+ @pytest.mark.api
127
+ @pytest.mark.testcase("EPMCDME-6708")
128
128
  def test_assistant_with_project_integration_only(
129
129
  integration_utils,
130
130
  assistant,
@@ -158,7 +158,7 @@ def test_assistant_with_project_integration_only(
158
158
  @pytest.mark.integration
159
159
  @pytest.mark.default_integration
160
160
  @pytest.mark.not_for_parallel_run
161
- @pytest.mark.regression
161
+ @pytest.mark.api
162
162
  def test_assistant_with_global_valid_and_user_invalid_integration(
163
163
  assistant, assistant_utils, integration_utils, similarity_check
164
164
  ):
@@ -198,7 +198,7 @@ def test_assistant_with_global_valid_and_user_invalid_integration(
198
198
  @pytest.mark.integration
199
199
  @pytest.mark.default_integration
200
200
  @pytest.mark.not_for_parallel_run
201
- @pytest.mark.regression
201
+ @pytest.mark.api
202
202
  def test_assistant_with_project_valid_and_user_invalid_integration(
203
203
  assistant, assistant_utils, integration_utils, similarity_check
204
204
  ):