tavernでCognitoから取得したID Tokenを利用したAPIテスト

APIのテストで[tavern](https://github.com/taverntesting/tavern)を使ってCognitoの認証情報使ったテストがしたかったので、調べた内容です。

## やりたいこと
- Cognitoから認証で取得したID Tokenを利用してAPIをテストしたい
- Cognitoの認証情報はSecretsManagerに保存する
- 環境毎に変数定義したい

## ディレクトリ構成
簡単な構成として以下のようなものを作りました。

```sh
$ tree
.
├── config
│   ├── dev.yaml
│   └── prd.yaml
├── helper
│   └── auth.py
├── pyproject.toml
└── tests
    ├── __init__.py
    └── myapi.tavern.yaml
```

`config/` の下は環境毎の設定ファイルを置いています。テスト実行時に引数でファイルを指定します。

```config/dev.yaml
variables:
  aws:
    secretName: tavern/test/dev # SecretsManagerのSecret名
    region: ap-northeast-1 # AWS Region
  service:
    baseUrl: https://your.domain.com # APIのBaseUrl
```

`helper/` の下にはヘルパー関数を置いて、SecretsManagerから取得した情報を元にID TokenをCognitoから取得しています。引数なども渡せます。

ちなみにSecretsManagerには以下のように認証情報置いています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116068/2963a577-d0e7-4f5e-94cd-95e8e75225d0.png)



```helper/auth.py
import ast
from dataclasses import dataclass
import boto3
from box import Box

@dataclass(frozen=True)
class CognitoCredentials:
    client_id: str
    username: str
    password: str

def _get_secrets(secret_name:str,aws_region:str) -> CognitoCredentials:
    """
    Get the secret from AWS Secrets Manager
    """
    secretsmanager = boto3.client("secretsmanager", region_name=aws_region)
    response = secretsmanager.get_secret_value(SecretId=secret_name)
    secret = ast.literal_eval(response["SecretString"])
    return CognitoCredentials(
        client_id=secret["clientid"],
        username=secret["username"],
        password=secret["password"],
    )


def get_authorization_header(secret_name:str,aws_region:str) -> Box:
    """
    Get the Cognito ID token
    """
    cred = _get_secrets(secret_name,aws_region)
    cognito_idp_client = boto3.client("cognito-idp", region_name=aws_region)
    res = cognito_idp_client.initiate_auth(
        AuthFlow="USER_PASSWORD_AUTH",
        AuthParameters={
            "USERNAME": cred.username,
            "PASSWORD": cred.password,
        },
        ClientId=cred.client_id,
    )
    id_token = res["AuthenticationResult"]["IdToken"]
    return Box({"Authorization": id_token})
```

`tests/` の下にテストファイルを置きます。
- `{}` の記法で `config/` の下のyamlファイルの値を変数として埋め込めます
- `function` で指定した関数に `extra_kwargs` で引数を渡しています

```yaml
test_name: Call API with Cognito ID token

stages:
  - name: Cognitoから取得したIDトークンを使ってAPIを実行
    request:
      url: "{service.baseUrl:s}/some/api"
      method: GET
      headers:
        $ext:
          function: helper.auth:get_authorization_header
          extra_kwargs:
            secret_name: "{aws.secretName:s}"
            aws_region: "{aws.region:s}"
    response:
      status_code: 200
```

pyproject.tomlではpytestの設定を追加しています。tavernで利用されます。

```pyproject.toml
[project]
name = "tavern-cognito-token"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "boto3>=1.36.21",
    "tavern[pytest]==2.11.0",
]

[tool.pytest.ini_options]
addopts = "-ra -s --log-cli-level=INFO"
testpaths = ["tests"]
```

## 実行してみる

以下のように `--tavern-global-cfg` でconfig配下の設定ファイルを指定して実行することで、環境切り替えつつテストが可能です。

```sh
uv run py.test --tavern-global-cfg=./config/dev.yaml 
```