OpenaAPI (Swagger) の管理が本当に煩わしい

OSA の管理は基本的にはサーバ側の責任なのかなと思います。
私はサーバサイドエンジニアですが、この管理が本当に大変だと身をもって感じています。
RESTでAPIを作るならSwaggerの仕様書が間違っているとFE/BEどちらも消耗してしまうこと山の如しです。
FEのエンジニアから「プロパティ名が一文字間違っていてハマりましたよー笑」なんて言われてしまうと、平謝りすることしかできないので、どうしてもこの辺りをちゃんとさせねばなりません。
(その説は本当にすみません)
しかし、工数が限られている中で、目視で何度も確認はあまりのも時間を消費しすぎてしまう。
そこで、自動テストツールであるDreddの出番です。
Dredd と 分割されたOAS
例えば以下のように、外部ファイルを参照させて分割したOASの場合、 Dreddはテストをしてくれません。
responses:
"204":
$ref: "responses/common/204.yml"
"401":
$ref: "responses/common/401.yml"
こうしたOASの場合、事前にファイルを統合する必要があります。
Swagger Merger
というわけで、ファイルの統合には Swagger Merger という素晴らしいライブラリがあります。
これを使ってファイルを統合し、その後に Dredd でテストで回しましょう。
Makefile でやっちゃおう
操作もそんなに複雑ではないし、Makefile大好きエンジニアなので、最初はMakefileを使って運用しました。
make dredd-init
でライブラリをインストールしてもらって、
make dredd
でテストを実行します。
openapi/default.yml がOASである場合、以下のように設定すればOK。
dredd-init:
@npm install swagger-merger -g
@npm install -g dredd
dredd:
@swagger-merger -i openapi/default.yml -o openapi/merged.yml
@dredd openapi/merged.yml http://localhost:8000/api/ -d --hookfiles="openapi/hooks/*.js"
Dredd テストの制御
デフォルトでは、OASに書かれたすべてのレスポンスを検証します。
例えばすべてのエンドポイントに401が設定されていると、それぞれ401のレスポンスを検証してしまいます。
hookファイルを利用することで、テストをスキップさせるなど、テストの挙動を制御することができます。
上記のコマンドのうち --hookfiles="openapi/hooks/*.js"
がどのhookファイルを利用するかを指定しています。
またhookファイルはjs以外の言語で書くこともできます。
認証が必要なエンドポイントについては、hookファイルで、特定のテストの前に Authorizationヘッダーにトークンをつけるなどの処理を挟まないと、正常系のテストができなかったりします。
Dockerを使おう
各自 npm を使ってインストールしてもらっていましたが、 nodebrew のバージョンの切り替えを忘れてて動かないと連絡があったり、環境の問題で変な不具合が出たりと割り込みが入るので、やっぱりDocker化することにしました。
最初はDocker Compose で作っていましたが、常時起動するものでもないし、なんとなく使い勝手が悪かったので、シェルスクリプトで記述することにしました。
また、hookファイルは大体必須なので、プロジェクトごとにいちいち配置するのも面倒だと考えたので、OASがあるディレクトリに引っ張ってきてそのまま使えたらいいなと考えました。
というわけで、作ったものを置いておきます↓
使い方は、 OASがあるディレクトリに git clone
して、 params.cfg でAPIのエンドポイントとOASのファイル名を指定するだけです。
↑スクリプトの解説
docker run -it --rm -v $SCRIPT_DIR/..:/openapi \
--add-host=host.docker.internal:host-gateway \
apiaryio/dredd sh -c \
"dredd openapi/$DREDD_DIR/tmp/merged.yml $API_URL \
-d --hookfiles=$HOOKFILES --no-color | \
tee openapi/$DREDD_DIR/tmp/dredd_result.txt | \
grep -e 'pass\:' -e 'fail\:' -e 'complete\:'"
コマンドの本体部分は上記のようになっています。
Docker を使っているのでコンテナからホストコンピュータのエンドポイントにアクセスするために--add-host=host.docker.internal:host-gateway
を使っています。
これは、Docker v 20.10.0 以降でないと利用できないので、それより古いバージョンを使っている人は、別途設定が必要になります。
tee openapi/$DREDD_DIR/tmp/dredd_result.txt | \
grep -e 'pass\:' -e 'fail\:' -e 'complete\:'"
この部分ですが、 Dreddのテスト結果はとんでもなく長いので、ダイジェストだけをSTDOUTに出力するようにして、詳細はファイルに出力するようにしています。
本当にとんでもなく長いのです。
Dreddハマりどころ
Swagger-UI とは無関係
私が勘違いしていたのですが、Dreddのコマンド中のエンドポイントの指定先をSwagger-UIなどにしてしまうことです。
DreddはSwagger-UIを自動で動かすツールではありません。
DreddはOASを見て、検証先のAPIを自動で叩くツールなのです。
例えばRailsのAPIモードが localhost:3000 で動いているなら、
dredd openapi/default.yml http://localhost:3000/api/
のようにコマンドを打つ必要があります。
OASの文法
openapiが推奨する書き方と違っていても、 Swagger-UI はよしなに解釈してUIを構築してくれます。
しかし、Dredd先生は大きな間違いがある場合にはテストを走らせてくれません。
私がひっかかった指摘が以下です
- description の項目がない
- object が正しく設定されていない
- responseでheaderの内容とcontentの内容が違い
- exampleの値が指定されていない
- そもそも exampleの値を見てリクエストをテストするので、これが指定されていないとテストができない
エンドポイント...
BEのリポジトリが2個存在するアプリで、Dreddを使っていたのですが、
Dreddのテスト中だけ、一方はJWT認証が通るのに、もう一方は認証が通らない!なんてことがありました。
SwaggerUIからは正常に動くのになぜ...
JWTの中身も別に間違ってないのに、なぜなんだと悩んでいましたが、DreddのAPIのエンドポイントの向き先が間違っていて、
もう一方のAPIに向いていて、JWTシークレットが違うから認証が通らないなんてことがありました。
ちなみに、エンドポイントが違うと気づくのにN時間かかり、かなり時間を浪費...
こういうこともあるので、エンドポイントは真っ先の確認は大事ですね!涙
Dreddの詳細な結果では、実際のレスポンスと、予想されたレスポンスが記載されるのですが、
実際のレスポンスが何もない場合、大体エンドポイントが違うだけです笑
こういうミスが無くなるように Makefile なり スクリプトなりを使って管理した方がいいなと思いました。
でも結局...
特に認証が入る場合、hookファイルのカスタマイズが必須で、これをプロジェクトに合ったものにしないと使い物になりません。
ただ、上記レポジトリのhook.jsの雛形と、以下のドキュメントを参考にすれば大体なんとかなると思います。
エンドポイントの並び順
OASのエンドポイントを上から順番で叩くので、hookファイルの作成もそこを気にする必要がああります。
- loginのエンドポイントを叩き、返ってきたトークンを Authroizationヘッダに載せる
- 続いてrefresh のエンドポイントを叩く
- 以下認証が必要なエンドポイントを順に叩く
こういう場合、refreshを叩くことで前のトークンが期限切れになる処理がある場合、当然1.で手に入れたトークンは使えなくなります。
しかし、これに気づかず、トークンの内容は正しいのになぜか 3.の正常系が動かずハマりました。