Metadata-Version: 2.5
Name: apitestx
Version: 0.1.0
Summary: A synchronous pytest and HTTPX API testing layer
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pytest<9,>=8
Provides-Extra: test
Requires-Dist: pytest-httpx<1,>=0.35; extra == 'test'
Description-Content-Type: text/markdown

# apitestx

`apitestx` 0.1.0 是基于 pytest 与 HTTPX 的同步 API 测试增强层，要求 Python 3.11+。非 2xx 响应不会自动抛错，由测试显式断言状态。

## 安装与运行

从 PyPI 安装可执行 `python -m pip install apitestx`；在尚未公开发布或离线环境中，也可安装本地 `dist/apitestx-0.1.0-py3-none-any.whl`。安装后包含 pytest 和 HTTPX。

开发或运行仓库内 Mock 示例：

```bash
python -m pip install -e ".[test]"
python -m pytest -q -p no:cacheprovider examples/test_workflow.py
```

安装后的 `pytest11` 入口自动提供每个测试函数独立的 `api` fixture，并在函数结束时关闭客户端。示例使用测试依赖 `pytest-httpx` 拦截请求，无需访问公网。

## 使用方式

```python
from apitestx import expect


def test_item(api, httpx_mock):
    httpx_mock.add_response(
        method="POST", url="https://service.test/items", status_code=201,
        json={"data": {"id": 9, "name": "Ada"}},
    )
    httpx_mock.add_response(
        method="GET", url="https://service.test/items/9", status_code=200,
        json={"data": {"id": 9, "name": "Ada"}},
    )

    api.auth.set_bearer("test-token")
    created = api.post("https://service.test/items", json={"name": "Ada"})
    expect(created).status(201).json_path("data.id").eq(9)
    api.variables.set("id", created.json_path("data.id"))
    detail = api.get(f"https://service.test/items/{api.variables.get('id')}")
    expect(detail).status(200).json_path("data.name").contains("Ad")
```

也可以使用 `APIClient(base_url="https://service.test")` 或环境变量 `APITESTX_BASE_URL` 配置基础地址；不配置时仅允许绝对 URL。自动设置的 Bearer 凭据仅发给配置的 Base URL 同源地址；未配置 Base URL 时绑定第一个带鉴权请求的 origin，跨域、跨端口或 HTTPS 降级的后续请求不自动携带。调用 `api.reset_state()` 可清除此绑定，需跨域鉴权时请显式传入对应请求的 `Authorization`。`APIClient` 可作为上下文管理器使用，支持注入 HTTPX `transport`。`api.get/post/put/patch/delete` 支持 `headers`、`params`、`json`、`data`、`timeout` 等 HTTPX 请求参数；`response.status_code/headers/text/json()/json_path()` 提供响应访问。

`json_path` 只支持如 `data.id`、`items[0].id` 的简单字段及数组索引路径。存在且值为 JSON `null` 时返回 `None`；路径不存在抛出 `JsonPathError`，非 JSON 抛出 `ResponseDecodeError`。变量仅支持 `function` 作用域，`module`/`session` 会抛 `NotImplementedError`；使用 `response.json_path` 手动提取、`api.variables.set/get` 传给后续请求，不做隐式模板替换。可使用 `api.variables.clear()` 或 `api.reset_state()` 清空函数内状态。

框架自身的异常与断言诊断不输出原始请求/响应体；敏感字段、URL 查询参数及 Bearer Token 在诊断中脱敏，可使用 `APIClient(sensitive_fields=("pin",))` 扩展敏感字段。`response.text`、`response.json()` 和 `RequestError.original_error` 仍允许测试代码主动访问原始数据：测试自身的打印、原生断言、`pytest --showlocals` 和第三方库日志不属于框架脱敏边界。

## 故障排查与范围

- 相对路径报 `ConfigurationError`：设置 `APITESTX_BASE_URL` 或在构造 `APIClient` 时传入 `base_url`，也可改用绝对 URL。
- 无法注入 `api` Fixture：确认包已经安装到当前 pytest 所用的 Python 环境；`pytest11` 入口只在安装后自动发现，直接拷贝源码运行不等于已安装。
- `JsonPathError`：确认路径是 `data.id` 或 `items[0].id` 一类简单路径；带点号的原始键请用 `response.json()` 按 Python 下标访问。`null` 与路径缺失不同。
- 网络测试失败：示例依赖测试可选项中的 `pytest-httpx`；完整测试无需真实外网 API。未提供 module/session 变量共享、隐式模板替换、异步客户端、YAML/CLI、OpenAPI 或 AI 功能。
- 输出 JUnit XML：使用 `python -m pytest --junitxml=report.xml`；不要对真实密钥启用 `--showlocals`，也不要在用户测试代码里直接打印秘密。

## 维护与版本更新

发布到公开 PyPI 后，先修改 `pyproject.toml` 的 `version` 和 `apitestx/__init__.py` 的 `__version__`，例如 `0.1.0 → 0.1.1`；运行测试、构建并上传**新版本**。PyPI 不允许用同名文件覆盖旧版本，即使撤回旧版也不能复用其文件名。运行 `python -m pip install --upgrade apitestx` 的用户才会获得新版本；需要复现旧环境时可固定版本 `python -m pip install apitestx==0.1.0`。

项目采用 MIT 许可，许可文本见 `LICENSE`；版权署名为 `apitestx contributors`。本仓库准备公开发布，但构建成功不代表已经上传 PyPI；以正式项目页面能够访问、从公开索引安装成功为准。正式发行包只包含经审查可公开的源码、许可和元数据，不包含工作资料、内部链接或密钥。
