mcp-api-test 0.1.2__tar.gz → 0.1.4__tar.gz

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.
Files changed (27) hide show
  1. mcp_api_test-0.1.4/PKG-INFO +112 -0
  2. mcp_api_test-0.1.4/README.md +81 -0
  3. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/pyproject.toml +5 -4
  4. mcp_api_test-0.1.4/src/flags.py +61 -0
  5. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/logger.py +8 -0
  6. mcp_api_test-0.1.4/src/mcp_api_test.egg-info/PKG-INFO +112 -0
  7. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/SOURCES.txt +4 -1
  8. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/requires.txt +4 -1
  9. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/top_level.txt +1 -0
  10. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/server.py +27 -9
  11. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/utils.py +14 -11
  12. mcp_api_test-0.1.4/tests/test_packaging.py +46 -0
  13. mcp_api_test-0.1.4/tests/test_parse_curl_command.py +70 -0
  14. mcp_api_test-0.1.4/tests/test_utils.py +23 -0
  15. mcp_api_test-0.1.2/PKG-INFO +0 -334
  16. mcp_api_test-0.1.2/README.md +0 -305
  17. mcp_api_test-0.1.2/src/mcp_api_test.egg-info/PKG-INFO +0 -334
  18. mcp_api_test-0.1.2/tests/test_parse_curl_command.py +0 -23
  19. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/LICENSE +0 -0
  20. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/MANIFEST.in +0 -0
  21. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/setup.cfg +0 -0
  22. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/__main__.py +0 -0
  23. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/dependency_links.txt +0 -0
  24. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/mcp_api_test.egg-info/entry_points.txt +0 -0
  25. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/src/models.py +0 -0
  26. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/tests/test_mcp_api_tools.py +0 -0
  27. {mcp_api_test-0.1.2 → mcp_api_test-0.1.4}/tests/test_parse_status_code.py +0 -0
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-api-test
3
+ Version: 0.1.4
4
+ Summary: MCP server for testing REST API with built-in assertions
5
+ Author: Ryan Febriansyah
6
+ License-Expression: MIT
7
+ Keywords: testing,mcp,llm
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Natural Language :: English
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: mcp<2.0.0,>=1.0.0
22
+ Requires-Dist: httpx
23
+ Requires-Dist: python-dotenv
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-asyncio; extra == "dev"
27
+ Requires-Dist: respx; extra == "dev"
28
+ Provides-Extra: docs
29
+ Requires-Dist: mkdocs-material; extra == "docs"
30
+ Dynamic: license-file
31
+
32
+ # mcp-api-test
33
+
34
+ ![pipeline](https://gitlab.com/ryaneatfood/mcp-api-testing/badges/master/pipeline.svg) ![PyPI License](https://img.shields.io/pypi/l/mcp-api-test) ![PyPI Python Version](https://img.shields.io/pypi/pyversions/mcp-api-test)
35
+
36
+ An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
37
+
38
+ Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
39
+
40
+ It complements (rather than replaces) tools like pytest, Postman, and Playwright.
41
+
42
+ ## Use cases
43
+
44
+ - **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
45
+ - **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
46
+ - **Quick validation**: test an endpoint inline during a coding session
47
+ - **CI/CD automation**: agents validate deployments autonomously in pipelines
48
+
49
+ > **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install mcp-api-test
55
+ ```
56
+
57
+ Or from source:
58
+
59
+ ```bash
60
+ git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
61
+ cd mcp-api-testing
62
+ pip install -e .
63
+ ```
64
+
65
+ Requires Python >= 3.10.
66
+
67
+ ## Quick start
68
+
69
+ Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
70
+
71
+ ```json
72
+ {
73
+ "mcp": {
74
+ "mcp-api-test": {
75
+ "type": "local",
76
+ "command": ["mcp-api-test"],
77
+ "enabled": true
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ For **Claude Desktop**, add to `claude_desktop_config.json`:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "mcp-api-test": {
89
+ "command": "mcp-api-test"
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ Then prompt your agent:
96
+
97
+ > "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
98
+
99
+ The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
100
+
101
+ ## Documentation
102
+
103
+ Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
104
+
105
+ - **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
106
+ - **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
107
+ - **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
108
+ - **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,81 @@
1
+ # mcp-api-test
2
+
3
+ ![pipeline](https://gitlab.com/ryaneatfood/mcp-api-testing/badges/master/pipeline.svg) ![PyPI License](https://img.shields.io/pypi/l/mcp-api-test) ![PyPI Python Version](https://img.shields.io/pypi/pyversions/mcp-api-test)
4
+
5
+ An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
6
+
7
+ Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
8
+
9
+ It complements (rather than replaces) tools like pytest, Postman, and Playwright.
10
+
11
+ ## Use cases
12
+
13
+ - **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
14
+ - **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
15
+ - **Quick validation**: test an endpoint inline during a coding session
16
+ - **CI/CD automation**: agents validate deployments autonomously in pipelines
17
+
18
+ > **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install mcp-api-test
24
+ ```
25
+
26
+ Or from source:
27
+
28
+ ```bash
29
+ git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
30
+ cd mcp-api-testing
31
+ pip install -e .
32
+ ```
33
+
34
+ Requires Python >= 3.10.
35
+
36
+ ## Quick start
37
+
38
+ Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
39
+
40
+ ```json
41
+ {
42
+ "mcp": {
43
+ "mcp-api-test": {
44
+ "type": "local",
45
+ "command": ["mcp-api-test"],
46
+ "enabled": true
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ For **Claude Desktop**, add to `claude_desktop_config.json`:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "mcp-api-test": {
58
+ "command": "mcp-api-test"
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Then prompt your agent:
65
+
66
+ > "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
67
+
68
+ The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
69
+
70
+ ## Documentation
71
+
72
+ Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
73
+
74
+ - **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
75
+ - **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
76
+ - **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
77
+ - **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
78
+
79
+ ## License
80
+
81
+ MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-api-test"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "MCP server for testing REST API with built-in assertions"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -10,7 +10,7 @@ authors = [
10
10
 
11
11
  requires-python = ">=3.10"
12
12
  dependencies = [
13
- "mcp",
13
+ "mcp>=1.0.0,<2.0.0",
14
14
  "httpx",
15
15
  "python-dotenv"
16
16
  ]
@@ -39,7 +39,8 @@ build-backend = "setuptools.build_meta"
39
39
 
40
40
  [tool.setuptools]
41
41
  package-dir = {"" = "src"}
42
- py-modules = ["server", "__main__", "logger", "models", "utils"]
42
+ py-modules = ["server", "__main__", "logger", "models", "utils", "flags"]
43
43
 
44
44
  [project.optional-dependencies]
45
- dev = ["pytest", "pytest-asyncio", "respx"]
45
+ dev = ["pytest", "pytest-asyncio", "respx"]
46
+ docs = ["mkdocs-material"]
@@ -0,0 +1,61 @@
1
+ """A collection of curl flag which is being used during HTTP request/inspection"""
2
+
3
+ # flags that consume the next token as their value,
4
+ # maps every recognize consuming flag to its semantic roles
5
+ CURL_VALUE_FLAGS: dict[str, str] = {
6
+ "-X": "method",
7
+ "--request": "method",
8
+ "-H": "header",
9
+ "--header": "header",
10
+ "-d": "data",
11
+ "--data": "data",
12
+ "--data-raw": "data", # browser DevTools "Copy as cURL" uses this
13
+ "--data-binary": "data", # common in API clients
14
+ "--data-urlencode": "data", # URL-encoded payloads
15
+ "-u": "user",
16
+ "--user": "user",
17
+ "--url": "url_flag", # explicit --url form
18
+ "-A": "user_agent",
19
+ "--user-agent": "user_agent",
20
+ "--max-time": "ignored",
21
+ "--connect-timeout": "ignored",
22
+ "-o": "ignored",
23
+ "--output": "ignored",
24
+ "--proxy": "ignored",
25
+ "-x": "ignored",
26
+ "-e": "ignored",
27
+ "--referer": "ignored",
28
+ }
29
+
30
+ # standalone flags with no following value
31
+ CURL_BOOLEAN_FLAGS: dict[str, str] = {
32
+ "-L",
33
+ "--location", # follow redirects, very common in browser exports
34
+ "-k",
35
+ "--insecure", # skip TLS verification
36
+ "-s",
37
+ "--silent",
38
+ "-v",
39
+ "--verbose",
40
+ "-i",
41
+ "--include",
42
+ "-I",
43
+ "--head",
44
+ "--compressed", # added by Chrome DevTools
45
+ "--http1.1",
46
+ "--http2",
47
+ "-G",
48
+ "--get",
49
+ "--no-keepalive",
50
+ "--fail",
51
+ "-f",
52
+ }
53
+
54
+ # flags that we recognize but want to surface with a warning for,
55
+ # emit a logger.warning with a human-readable explanation
56
+ CURL_WARN_FLAGS: dict[str, str] = {
57
+ "-k": "--insecure is not supported: TLS verification will still be enforced",
58
+ "--insecure": "--insecure is not supported: TLS verification will still be enforced",
59
+ "-u": "-u/--user (basic auth) is not supported: add an Authorization header explicitly",
60
+ "--user": "-u/--user (basic auth) is not supported: add an Authorization header explicitly",
61
+ }
@@ -39,6 +39,14 @@ class Logger:
39
39
  message = message % args
40
40
  self.logger.info(message, **kwargs)
41
41
 
42
+ def warning(self, message: str, *args, **kwargs) -> None:
43
+ """
44
+ Log a warning message with particular arguments
45
+ """
46
+ if args:
47
+ message = message % args
48
+ self.logger.warning(message, **kwargs)
49
+
42
50
  def error(self, message: str, *args, **kwargs) -> None:
43
51
  """
44
52
  Log an error message with particular arguments
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: mcp-api-test
3
+ Version: 0.1.4
4
+ Summary: MCP server for testing REST API with built-in assertions
5
+ Author: Ryan Febriansyah
6
+ License-Expression: MIT
7
+ Keywords: testing,mcp,llm
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Natural Language :: English
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Quality Assurance
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: mcp<2.0.0,>=1.0.0
22
+ Requires-Dist: httpx
23
+ Requires-Dist: python-dotenv
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Requires-Dist: pytest-asyncio; extra == "dev"
27
+ Requires-Dist: respx; extra == "dev"
28
+ Provides-Extra: docs
29
+ Requires-Dist: mkdocs-material; extra == "docs"
30
+ Dynamic: license-file
31
+
32
+ # mcp-api-test
33
+
34
+ ![pipeline](https://gitlab.com/ryaneatfood/mcp-api-testing/badges/master/pipeline.svg) ![PyPI License](https://img.shields.io/pypi/l/mcp-api-test) ![PyPI Python Version](https://img.shields.io/pypi/pyversions/mcp-api-test)
35
+
36
+ An AI-native API testing engine that enables LLM agents to execute, validate, and reason about REST APIs using natural language or cURL inputs.
37
+
38
+ Unlike traditional testing frameworks, `mcp-api-test` exposes API testing as a **first-class capability for AI agents** rather than a developer-facing tool. LLM-powered clients invoke the `run_test_api` tool directly, making API validation a building block for autonomous coding agents, not a separate human-driven workflow.
39
+
40
+ It complements (rather than replaces) tools like pytest, Postman, and Playwright.
41
+
42
+ ## Use cases
43
+
44
+ - **QA & test engineering**: prompt an agent with natural language instead of crafting Postman requests
45
+ - **Post-deployment regression**: have an agent smoke-test critical paths after a deploy
46
+ - **Quick validation**: test an endpoint inline during a coding session
47
+ - **CI/CD automation**: agents validate deployments autonomously in pipelines
48
+
49
+ > **Token efficiency.** Agents get deterministic, structured results in a single tool call instead of parsing raw `curl` output. Fewer tokens, fewer hallucinations.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install mcp-api-test
55
+ ```
56
+
57
+ Or from source:
58
+
59
+ ```bash
60
+ git clone git@gitlab.com:ryaneatfood/mcp-api-testing.git
61
+ cd mcp-api-testing
62
+ pip install -e .
63
+ ```
64
+
65
+ Requires Python >= 3.10.
66
+
67
+ ## Quick start
68
+
69
+ Configure your MCP client to use the server. For **OpenCode**, add to `opencode.json`:
70
+
71
+ ```json
72
+ {
73
+ "mcp": {
74
+ "mcp-api-test": {
75
+ "type": "local",
76
+ "command": ["mcp-api-test"],
77
+ "enabled": true
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ For **Claude Desktop**, add to `claude_desktop_config.json`:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "mcp-api-test": {
89
+ "command": "mcp-api-test"
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ Then prompt your agent:
96
+
97
+ > "Test this API: `curl -X POST https://api.example.com/v1/users -H 'Authorization: Bearer xxx' -d '{"name":"test"}'`, ensure status code is 201 and response contains 'id'"
98
+
99
+ The agent invokes `run_test_api`, validates against the expected contract, and returns structured pass/fail results.
100
+
101
+ ## Documentation
102
+
103
+ Full documentation is hosted on this [page](https://ryaneatfood.gitlab.io/mcp-api-testing/), and another way to navigate the docs:
104
+
105
+ - **[Getting Started](docs/getting-started/installation.md)**: installation and client setup
106
+ - **[Usage](docs/usage/request-modes.md)**: cURL mode, structured mode, assertions, response format
107
+ - **[Guides](docs/guides/troubleshooting.md)**: troubleshooting, development, configuration
108
+ - **[Reference](docs/reference/tool-parameters.md)**: full parameter reference
109
+
110
+ ## License
111
+
112
+ MIT
@@ -3,6 +3,7 @@ MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
5
  src/__main__.py
6
+ src/flags.py
6
7
  src/logger.py
7
8
  src/models.py
8
9
  src/server.py
@@ -14,5 +15,7 @@ src/mcp_api_test.egg-info/entry_points.txt
14
15
  src/mcp_api_test.egg-info/requires.txt
15
16
  src/mcp_api_test.egg-info/top_level.txt
16
17
  tests/test_mcp_api_tools.py
18
+ tests/test_packaging.py
17
19
  tests/test_parse_curl_command.py
18
- tests/test_parse_status_code.py
20
+ tests/test_parse_status_code.py
21
+ tests/test_utils.py
@@ -1,4 +1,4 @@
1
- mcp
1
+ mcp<2.0.0,>=1.0.0
2
2
  httpx
3
3
  python-dotenv
4
4
 
@@ -6,3 +6,6 @@ python-dotenv
6
6
  pytest
7
7
  pytest-asyncio
8
8
  respx
9
+
10
+ [docs]
11
+ mkdocs-material
@@ -1,4 +1,5 @@
1
1
  __main__
2
+ flags
2
3
  logger
3
4
  models
4
5
  server
@@ -80,7 +80,7 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
80
80
 
81
81
  # emit warning for supported but warnin changing flags
82
82
  if token in CURL_WARN_FLAGS:
83
- logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}`")
83
+ logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}")
84
84
 
85
85
  if role == "method":
86
86
  method = value
@@ -96,11 +96,13 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
96
96
  payload = json.loads(raw_data)
97
97
  except json.JSONDecodeError:
98
98
  logger.error(
99
- f"[curl parser] payload for '{token} is not valid JSON"
99
+ f"[curl parser] payload for '{token}' is not valid JSON"
100
100
  )
101
101
  payload = raw_data
102
102
  elif role == "url_flag":
103
103
  url = value
104
+ elif role == "user_agent":
105
+ headers["User-Agent"] = value
104
106
 
105
107
  # everything else is consumed silently
106
108
  index += 2
@@ -109,7 +111,7 @@ def parse_curl_command(command: str) -> Dict[str, Any]:
109
111
  # boolean standalone flags
110
112
  if token in CURL_BOOLEAN_FLAGS:
111
113
  if token in CURL_WARN_FLAGS:
112
- logger.info(f"[curl parser] {CURL_WARN_FLAGS[token]}")
114
+ logger.warning(message=f"[curl parser] {CURL_WARN_FLAGS[token]}")
113
115
  index += 1
114
116
  continue
115
117
 
@@ -193,10 +195,15 @@ async def run_test_api(
193
195
  start_time = time.perf_counter()
194
196
  try:
195
197
  async with httpx.AsyncClient(timeout=30) as client:
196
- # prevent requests payload with None-type object or empty body
197
198
  kwargs = {"method": method, "url": full_path_url, "headers": headers}
198
199
  if payload is not None:
199
- kwargs["json"] = payload
200
+ # cURL parser may produce a raw string for non-JSON -d payloads
201
+ # (example: form-encoded data). Use content= to send as-is rather
202
+ # than json= which would double-encode the string.
203
+ if isinstance(payload, str):
204
+ kwargs["content"] = payload
205
+ else:
206
+ kwargs["json"] = payload
200
207
  response = await client.request(**kwargs)
201
208
  end_time = time.perf_counter()
202
209
  response_time_ms = (end_time - start_time) * 1000
@@ -245,9 +252,20 @@ async def run_test_api(
245
252
  if required_response_fields:
246
253
  for field in required_response_fields:
247
254
  if isinstance(resp_body, dict):
248
- # resolve conflict resolution about dot-nested structure
249
255
  is_exist = get_nested_fields(body=resp_body, field_path=field)
256
+ elif isinstance(resp_body, list):
257
+ # For JSON arrays, check whether at least one item
258
+ # (if dict) contains the expected field.
259
+ is_exist = any(
260
+ (
261
+ get_nested_fields(body=item, field_path=field)
262
+ if isinstance(item, dict)
263
+ else field in str(item)
264
+ )
265
+ for item in resp_body
266
+ )
250
267
  else:
268
+ # Scalar or plain-text response like substring match.
251
269
  is_exist = field in str(resp_body)
252
270
  assertions.append(
253
271
  {
@@ -283,9 +301,9 @@ async def run_test_api(
283
301
  )
284
302
  )
285
303
 
286
- overall_result = (
287
- all(a["passed"] for a in assertions) if assertions else response.is_success
288
- )
304
+ # When no assertions are provided there is nothing to check,
305
+ # set default to False so the user can see the raw response and decide.
306
+ overall_result = all(a["passed"] for a in assertions) if assertions else False
289
307
 
290
308
  result = APITestResult(
291
309
  api_name=api_name,
@@ -31,16 +31,19 @@ def render_console_template_result(result: APITestResult) -> str:
31
31
  lines.append("Assertions")
32
32
  lines.append(divider)
33
33
 
34
- for assertion in result.assertions:
35
- symbol = "✓" if assertion.passed else "x"
36
- lines.append(f"{symbol} {assertion.assertion}")
37
-
38
- lines.append("")
39
- lines.append("Summary")
40
- lines.append(divider)
41
- lines.append(f"Total Assertions : {len(result.assertions)}")
42
- lines.append(f"Passed : {passed_assertions}")
43
- lines.append(f"Failed : {failed_assertions}")
34
+ if result.assertions:
35
+ for assertion in result.assertions:
36
+ symbol = "✓" if assertion.passed else "x"
37
+ lines.append(f"{symbol} {assertion.assertion}")
38
+
39
+ lines.append("")
40
+ lines.append("Summary")
41
+ lines.append(divider)
42
+ lines.append(f"Total Assertions : {len(result.assertions)}")
43
+ lines.append(f"Passed : {passed_assertions}")
44
+ lines.append(f"Failed : {failed_assertions}")
45
+ else:
46
+ lines.append(" (no assertions provided)")
44
47
 
45
48
  return "\n".join(lines)
46
49
 
@@ -105,7 +108,7 @@ def build_fullpath_url(base_url: str, endpoint: str) -> str:
105
108
  _endpoint = endpoint.strip()
106
109
 
107
110
  if not _endpoint:
108
- endpoint = "/"
111
+ _endpoint = "/"
109
112
 
110
113
  if not _endpoint.startswith("/"):
111
114
  # automaticaly prepend the '/' before raise an assertion
@@ -0,0 +1,46 @@
1
+ """Static checks that the source layout matches the wheel packaging config"""
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+
6
+ PROJECT_ROOT = Path(__file__).resolve().parents[1]
7
+ SRC_DIR = PROJECT_ROOT / "src"
8
+
9
+
10
+ def _packaged_modules() -> list[str]:
11
+ with open(PROJECT_ROOT / "pyproject.toml", "rb") as f:
12
+ config = tomllib.load(f)
13
+ return config["tool"]["setuptools"]["py-modules"]
14
+
15
+
16
+ def _project_version() -> str:
17
+ with open(PROJECT_ROOT / "pyproject.toml", "rb") as f:
18
+ config = tomllib.load(f)
19
+ return config["project"]["version"]
20
+
21
+
22
+ def test_all_src_modules_are_packaged():
23
+ src_modules = {
24
+ p.stem
25
+ for p in SRC_DIR.glob("*.py")
26
+ if p.name != "__init__.py" and not p.name.startswith("__")
27
+ }
28
+ missing = src_modules - set(_packaged_modules())
29
+ assert not missing, (
30
+ f"Modules in src/ that are missing from pyproject.toml "
31
+ f"[tool.setuptools] py-modules: {sorted(missing)}"
32
+ )
33
+
34
+
35
+ def test_packaged_modules_exist_in_src():
36
+ for module in _packaged_modules():
37
+ assert (
38
+ SRC_DIR / f"{module}.py"
39
+ ).exists(), f"'{module}' is listed in py-modules but has no src/{module}.py"
40
+
41
+
42
+ def test_version_matches_pyproject():
43
+ # regression: src/__init__.py __version__ drifted from pyproject.toml
44
+ import src
45
+
46
+ assert src.__version__ == _project_version()