---
name: testany-sync-case-env-from-source
description: 从 OpenAPI 生成的 Testany case 源代码中自动识别入参和出参，并同步到 case 的环境变量列表中。适用于源代码存储在需要认证的 Git 仓库（如 Bitbucket）的场景。
---

# Testany Case 环境变量同步 Skill

本 Skill 用于从 OpenAPI definition 生成的 Testany case 源代码中自动提取入参和出参，并将它们同步更新到 Testany case 的 `environment_variables` 配置中。

## 适用场景

当 Testany case 的源代码满足以下条件时使用本 Skill：
- 源代码由 OpenAPI definition 自动生成
- 源代码包含 `BASE_STRUCTURES_INFO` 和 `relay_request_fields` 等特征结构
- 源代码存储在需要认证的 Git 仓库（如私有 Bitbucket 仓库）
- 需要将代码中的 ENV[...] 变量和出参字段同步到 case 配置

## 用户交互流程

### 步骤 1：选择操作模式

用户首先选择操作模式：

| 模式 | 说明 |
|------|------|
| **单 Case 操作** | 直接输入一个 case key |
| **批量 Keys 操作** | 输入多个 case key（逗号分隔） |
| **搜索批量操作** | 通过条件搜索 case，对结果进行批量操作 |

使用 `AskUserQuestion` 工具让用户选择模式。

---

### 步骤 2：获取 Case 列表

根据用户选择的模式，获取待处理的 case 列表：

#### 模式 A：单 Case 操作
```python
case_keys = [user_input_case_key]
```

#### 模式 B：批量 Keys 操作
```python
# 解析逗号分隔的 keys
case_keys = user_input_keys.split(',')
case_keys = [key.strip() for key in case_keys if key.strip()]
```

#### 模式 C：搜索批量操作
```python
# 使用 AskUserQuestion 询问搜索条件
# 可选搜索条件：
# - key_or_name: case key 或名称关键词
# - workspaces: 工作空间列表
# - case_labels: 标签列表
# - runtime_uuid: 运行时 UUID

# 调用 testany_list_cases 获取匹配的 case 列表
cases = testany_list_cases(
    key_or_name=search_keyword,
    workspaces=selected_workspaces,
    case_labels=selected_labels,
    page_size=100  # 限制单页结果数量
)

case_keys = [case['key'] for case in cases]
```

**搜索参数说明：**
- `key_or_name`: 模糊匹配 case key 或名称
- `workspaces`: 筛选指定工作空间的 case（需要先调用 `testany_get_my_workspaces` 获取可用工作空间列表）
- `case_labels`: 筛选包含指定标签的 case（需要先调用 `testany_list_labels` 获取可用标签列表）
- `page_size`: 单页返回数量，默认 20，批量操作建议设为 100

---

### 步骤 3：对每个 Case 执行同步流程

遍历 `case_keys`，对每个 case 执行以下子流程：

#### 子流程 3.1：验证 Case 存在

```python
case = testany_get_case(key=case_key)
if case is None or case.get('error'):
    记录错误：Case 不存在
    跳过此 case
    continue
```

#### 子流程 3.2：解析源代码 URL

从 `case['script_url']` 中提取：
- 仓库名称（如：testany-io/integration-test）
- 分支名称（如：demo1、from-swagger-backend-all）
- 文件路径（如：testany-scripts/xxx/yyy/xxx.py）

URL 格式：
```
https://bitbucket.org/{owner}/{repo}/src/{branch}/{file_path}
```

显示解析结果供用户确认：
- 分支名称
- 文件路径

#### 子流程 3.3：拉取源代码到本地

**使用 Git Worktree（默认方案）：**

```bash
# 统一工作树目录
WORKTREE_BASE="/tmp/testany-worktrees"

# 首次访问某个分支时，创建工作树（如果不存在）
BRANCH_WORKTREE="${WORKTREE_BASE}/{branch_name}"
if [ ! -d "${BRANCH_WORKTREE}" ]; then
    git fetch origin {branch_name}
    git worktree add "${BRANCH_WORKTREE}" origin/{branch_name}
fi

# 从工作树中读取文件
FILE_PATH="${BRANCH_WORKTREE}/{file_path}"
```

**优势：**
- 并行访问多个分支：每个分支有独立工作树
- 缓存复用：同一分支的多个 case 只需创建一次
- 无副作用：不影响主工作区的 Git 配置

#### 子流程 3.4：解析入参（Input Parameters）

从源代码的 `BASE_STRUCTURES_INFO["structure"]` 部分查找所有 `ENV[变量名]` 格式的占位符：

```python
import re

env_pattern = r'ENV\[([^\]]+)\]'
input_params = set()

for section in ['path', 'query', 'header', 'body']:
    if section in structure:
        text = json.dumps(structure[section])
        matches = re.findall(env_pattern, text)
        input_params.update(matches)
```

**入参规范：**
- 变量名保持 ENV[...] 中的原始名称（全大写）
- 类型：`env`（表示输入参数）
- value：使用占位符值 "PLACEHOLDER"
- description：根据参数位置和用途自动生成

#### 子流程 3.5：解析出参（Output Parameters）

从源代码的 `relay_request_fields` 数组中提取所有字段：

```python
relay_request_fields = [
    "NAME",
    "DESCRIPTION",
    "RUNTIME_UUID",
    # ...
]
```

**出参规范：**
- 字段名保持数组中的原始名称（全大写）
- 类型：`output`（表示 relay 输出参数）
- value：使用占位符值 "PLACEHOLDER"
- description：根据字段名称自动生成

#### 子流程 3.6：读取环境变量文件并替换 PLACEHOLDER

从同一分支的 env 目录中读取环境变量配置文件，并将匹配的入参 `value` 从 `"PLACEHOLDER"` 替换为实际值。

**环境变量文件路径规则：**

| 元素 | 示例 |
|------|------|
| Python 文件路径 | `testany-scripts/get_case_importHistory_id_payload/positive/test_get_case_importHistory_id_payload_positive.py` |
| 提取的目录名 | `get_case_importHistory_id_payload`（`testany-scripts/` 后的第一个目录） |
| 环境变量文件路径 | `integration-test/testany-scripts/env/{目录名}_environment.txt` |
| 完整路径 | `integration-test/testany-scripts/env/get_case_importHistory_id_payload_environment.txt` |

**实现逻辑：**

```python
# 步骤 1：从 Python 文件路径中提取目录名
import re
from pathlib import Path

python_path = "testany-scripts/get_case_importHistory_id_payload/positive/xxx.py"

# 提取 testany-scripts/ 后的第一个目录名
match = re.match(r'testany-scripts/([^/]+)/', python_path)
if match:
    directory_name = match.group(1)  # get_case_importHistory_id_payload
else:
    directory_name = None

# 步骤 2：构建环境变量文件路径
if directory_name:
    env_file_path = f"integration-test/testany-scripts/env/{directory_name}_environment.txt"
    # 在 worktree 中的完整路径
    full_env_path = f"{BRANCH_WORKTREE}/{env_file_path}"
else:
    full_env_path = None

# 步骤 3：读取并解析环境变量文件
env_values = {}
if full_env_path and Path(full_env_path).exists():
    with open(full_env_path, 'r') as f:
        for line in f:
            line = line.strip()
            if line and '=' in line and not line.startswith('#'):
                key, value = line.split('=', 1)
                env_values[key.strip()] = value.strip()

# 步骤 4：替换匹配的入参的 value
for param in input_params:
    if param in env_values:
        # 将 PLACEHOLDER 替换为实际值
        param_value = env_values[param]
        environment_variables.append({
            "type": "env",
            "name": param,
            "value": param_value,
            "description": f"Input parameter: {param}"
        })
    else:
        # 文件中未找到该参数，保留 PLACEHOLDER
        environment_variables.append({
            "type": "env",
            "name": param,
            "value": "PLACEHOLDER",
            "description": f"Input parameter: {param}"
        })
```

**环境变量文件格式：**

```
# 示例：get_case_importHistory_id_payload_environment.txt
ENV_QUERY_KEY_1_00=IXX
ENV_PATH_ID_1_00=12345
ENV_HEADER_AUTH_TOKEN=abc123def456
```

**注意：**
- 文件格式为 `KEY=VALUE`，每行一个变量
- 支持 `#` 开头的注释行（忽略）
- 如果文件不存在，所有入参的 `value` 保留 `"PLACEHOLDER"`
- 如果某个参数在文件中未找到，该参数的 `value` 保留 `"PLACEHOLDER"`

#### 子流程 3.7：预览更新

显示将要更新的 `environment_variables` 数组：

```
入参（type=env）：
- ENV_PATH_ID_1_00: Path parameter for import history id

出参（type=output）：
- NAME: Output field from response: NAME
- DESCRIPTION: Output field from response: DESCRIPTION
...
```

使用 `AskUserQuestion` 确认是否执行更新：
- 选项 1：确认更新
- 选项 2：跳过此 case
- 选项 3：跳过剩余所有 case

#### 子流程 3.8：执行更新

```python
# 注意：environment_variables 已经在子流程 3.6 中填充了入参
# 这里只需要添加出参（出参不替换 PLACEHOLDER）

# 添加出参
for field in relay_request_fields:
    environment_variables.append({
        "type": "output",
        "name": field,
        "value": "PLACEHOLDER",
        "description": f"Output field from response: {field}"
    })

# 更新 case
result = testany_update_case(
    key=case_key,
    case_meta={
        "environment_variables": environment_variables
    }
)
```

记录更新结果（成功/失败/错误信息）。

---

### 步骤 4：显示汇总报告

所有 case 处理完成后，显示汇总报告：

```
同步完成汇总：
----------------
总处理数：10
成功：8
失败：2

失败的 Case：
- ABC12345: Case 不存在
- DEF67890: 源代码拉取失败（分支不存在）

是否清理 worktree 缓存？（缓存可复用，建议保留）
```

提供清理选项：
- 选项 1：清理所有 worktree 缓存
- 选项 2：保留缓存（下次运行更快）

---

## 关键代码模式识别

### 入参模式

| 代码特征 | 含义 |
|---------|------|
| `BASE_STRUCTURES_INFO` | 请求结构模板 |
| `structure.path` | 路径参数 |
| `structure.query` | 查询参数 |
| `structure.header` | 请求头参数 |
| `structure.body` | 请求体参数 |
| `ENV[变量名]` | 需要解析的环境变量（入参） |

### 出参模式

| 代码特征 | 含义 |
|---------|------|
| `relay_request_fields` | 出参字段列表（全大写） |
| `response_fields` | 响应字段定义（用于 process_response_data） |
| `share_test_data(processed_data)` | 调用 relay 服务传递出参 |

---

## 错误处理

### 1. Case 不存在
- 检查 `testany_get_case` 的返回值
- 如果 404 错误，记录并跳过

### 2. 源代码 URL 解析失败
- 验证 script_url 格式是否符合预期
- 如果格式不匹配，记录错误并跳过

### 3. 分支不存在
- 执行 `git fetch` 时检查返回值
- 如果分支不存在，记录错误并跳过

### 4. 文件拉取失败
- 检查文件路径是否正确
- 如果拉取失败，记录错误并跳过

### 5. 代码模式不匹配
- 检查 `BASE_STRUCTURES_INFO` 和 `relay_request_fields` 是否存在
- 如果不存在，提示该 case 可能不是 OpenAPI 生成，记录并跳过

### 6. 环境变量更新失败
- 检查 `testany_update_case` 的错误信息
- 记录错误详情，继续处理下一个 case

---

## 使用示例

### 示例 1：单 Case 同步

```
/testany-sync-case-env-from-source
→ 选择操作模式：单 Case 操作
→ 输入 case key: B9121897
→ 执行同步流程...
```

### 示例 2：批量 Keys 同步

```
/testany-sync-case-env-from-source
→ 选择操作模式：批量 Keys 操作
→ 输入 case keys（逗号分隔）: B9121897,5C5FF07F,ABC12345
→ 执行同步流程...
```

### 示例 3：搜索批量操作

```
/testany-sync-case-env-from-source
→ 选择操作模式：搜索批量操作
→ 选择搜索条件：按标签搜索
→ 选择标签: swagger-backend
→ 找到 15 个匹配的 case
→ 执行同步流程...
```

### 示例 4：搜索条件组合

```
/testany-sync-case-env-from-source
→ 选择操作模式：搜索批量操作
→ 搜索关键词: importHistory
→ 选择工作空间: demo1
→ 选择标签: swagger-backend
→ 找到 8 个匹配的 case
→ 执行同步流程...
```

---

## 注意事项

### 1. 代码拉取方式
- 默认使用 Git Worktree：在 `/tmp/testany-worktrees/{branch_name}` 创建工作树
- Worktree 会缓存复用，同一分支的多个 case 只需创建一次
- 处理完成后可选择保留或清理缓存

### 2. 批量处理性能
- 不同分支的 case 可以并行处理（每个分支有独立 worktree）
- 同一分支的多个 case 可以快速复用已创建的 worktree
- 建议首次运行前用单 case 测试，确认配置正确

### 3. 搜索结果限制
- `testany_list_cases` 默认返回 20 条结果
- 批量操作建议设置 `page_size=100` 或更高
- 如需处理更多结果，需要分页处理

### 4. 变量命名
- 入参：保持 ENV[...] 中的原始名称
- 出参：保持 relay_request_fields 中的原始名称
- 所有变量名全大写

### 5. 值占位符与实际值
- 入参：从 `integration-test/testany-scripts/env/{目录名}_environment.txt` 文件中读取实际值替换 PLACEHOLDER
  - 如果文件存在且包含该参数，使用文件中的值（如 `ENV_QUERY_KEY_1_00=IXX`）
  - 如果文件不存在或参数未在文件中找到，保留 "PLACEHOLDER"
- 出参：始终保持 "PLACEHOLDER"（出参不需要从文件读取）
- description 字段提供有意义的说明

### 6. 环境变量文件
- 文件格式：`KEY=VALUE`，每行一个变量
- 支持 `#` 开头的注释行（自动忽略）
- 文件路径：`integration-test/testany-scripts/env/{目录名}_environment.txt`
- 目录名提取规则：从 `testany-scripts/` 后的第一个目录名获取
- 文件在同一分支的 worktree 中读取（需要先拉取代码到本地）

### 7. Git 配置
- 确保用户有正确的 Git 认证配置
- 私有仓库需要 SSH 或 HTTPS 认证
- 确保 origin remote 指向正确的仓库

---

## 成功标准

- 成功拉取源代码到本地
- 正确识别所有 ENV[...] 入参
- 正确识别所有 relay_request_fields 出参
- **从 env 目录成功读取环境变量文件并替换入参的 PLACEHOLDER**
- 成功更新 case 的 environment_variables
- 提供清晰的进度反馈和结果报告
- 支持 3 种操作模式（单 case、批量 keys、搜索批量）
- 提供汇总报告和清理选项

---

## 参考代码结构

OpenAPI 生成的典型测试代码结构：

```python
BASE_STRUCTURES_INFO = {
    "structure": {
        "path": {
            "id": "ENV[ENV_PATH_ID_1_00]"
        },
        "query": {},
        "header": {},
        "body": None
    }
}

# ... 其他代码 ...

relay_request_fields = [
    "NAME",
    "DESCRIPTION",
    "RUNTIME_UUID",
    "SCRIPT_ADDRESS",
    "CASE_META",
    "CASE_LABELS",
    "CASE_VERSION",
    "IS_PRIVATE",
    "WORKSPACE_KEYS",
    "CREDENTIALS",
]

response_fields = [
    # ... 响应字段定义 ...
]
```

---

## 实现检查清单

- [ ] 实现操作模式选择（单 case / 批量 keys / 搜索批量）
- [ ] 验证 case 存在
- [ ] 解析 script_url 获取仓库、分支、文件路径
- [ ] 检查并创建 Git Worktree（如不存在）
- [ ] 从 worktree 读取源代码文件
- [ ] 解析入参（ENV[...] 模式）
- [ ] 解析出参（relay_request_fields）
- [ ] **从 Python 文件路径提取目录名并构建环境变量文件路径**
- [ ] **从 worktree 读取环境变量文件（KEY=VALUE 格式）**
- [ ] **将匹配的入参 value 从 PLACEHOLDER 替换为实际值**
- [ ] 构建环境变量数组
- [ ] 预览更新结果（用户确认）
- [ ] 调用 testany_update_case
- [ ] 支持批量处理和多分支
- [ ] 生成汇总报告
- [ ] 提供清理 worktree 缓存选项

---

## Git Worktree 清理命令

手动清理所有 worktree 缓存：

```bash
# 列出所有 worktree
git worktree list

# 删除特定 worktree
git worktree remove /tmp/testany-worktrees/{branch_name}

# 删除所有 worktree（谨慎使用）
for worktree in /tmp/testany-worktrees/*/; do
    git worktree remove "$worktree"
done
```
