Tavernを使ってREST APIのテストをCLIから実行してみる

  • API単位のテストを自動化したい
    • 色々なツールがある
    • ぱっと見の印象は以下の通り
      • karateとかREST-Assured: 高機能だけど、Javaの知識が必要なことがネック。私一人ならともかく、GradleやMavenの仕組みからほかの人に教えて立ち上げるのはつらい...。DSLは羨ましい。
      • postman + newman: Collectionsのフォーマットがイマイチなのと、基本GUIなのがいまいち。企業もバックにいて一番開発が盛んそうではある。
      • tavern: よさそう。エコシステムが弱そうなのはネックかもしれない。
    • ひとまず、シンプルに導入できそうなtavernを試してみよう
  • 以下は個人的な作業メモです。

インストール

pip install tavern

試しに使ってみる

Postman Echoに対してリクエストを送ってみます。

以下のyamltest_postman_echo.tavern.yamlとして保存する。

test_name: Testing Postman Echo

stages:
  - name: Get Request
    request:
      url: https://postman-echo.com/get
      params:
        foo1: "bar1"
        foo2: "bar2"      
      headers:
        Authorization: "hoge"
      method: GET
    response:
      strict: # response のheadersではリクエストで明示的に設定したHeader以外があってもOKにするため
        - json:off
      status_code: 200
      json:
        args:  
          foo1: "bar1"
          foo2: "bar2"
        headers: 
          authorization: "hoge"
        url: "https://postman-echo.com/get?foo1=bar1&foo2=bar2"

そして、テストを実行してみると、出力は以下のような形で出力される。

tavern-ci.exe ./test_postman_echo.tavern.yaml

========================================================================================================================================== test session starts ===========================================================================================================================================
platform win32 -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: <path/to/rootdir>
plugins: tavern-1.19.0
collected 1 item

test_postman_echo.tavern.yaml .                                                                                                                                                                                                                                                                     [100%]

============================================================================================================================================ warnings summary ============================================================================================================================================ 
test_postman_echo.tavern.yaml::Testing Postman Echo
  <frozen importlib._bootstrap>:283: DeprecationWarning: the load_module() method is deprecated and slated for removal in Python 3.12; use exec_module() instead

-- Docs: https://docs.pytest.org/en/stable/warnings.html
====================================================================================================================================== 1 passed, 1 warning in 1.17s ====================================================================================================================================== 

使ってみて

  • エラーの表示に癖があって、ちょっと読み解くのに慣れが必要そう
  • 外部のyammlファイルを読み込むとエラーになる事象の解消。
  • 外部関数の呼び出しを試す。

PostgreSQLのトリガーをデバックする方法

ちょっとトリガーを触る機会があって、デバックする良い方法がないかな、と探したのでメモ。 基本的には、debugging postgresql trigger - Stack Overflowに書いてあることの一部試してみた、という記事。

EXPLAIN ANALYZEでTriggerが実行されたか確認する

実行計画を表示するために使うEXPLAINだが、EXPLAIN ANALYZEすると実際にSQL文を実行してくれる。

この時、そのSQLでトリガーが呼び出された場合は、その実行結果(実行時間、回数)も一緒に表示してくれるので、意図した呼び出しが行われているか確認できる。

PL/pgSQLRAISE文でプリントデバックする

トリガーで呼び出す関数を作成するPL/pgSQLにはRAISE文という、エラーをメッセージとして出力したり、そもそもエラーとして処理を停止させるための仕組みがある。

これを利用すると、トリガー内の任意の箇所で、メッセージを出力できるので、プリントデバックが可能。*1

42.9. エラーとメッセージ

試してみる

前提の環境

dockerで適当なコンテナを立ち上げて、接続する。

docker --version
# Docker version 20.10.8, build 3967b7d

sudo docker run --name example-postgres -e POSTGRES_PASSWORD=hoge -d 
# postgres

docker exec -it example-postgres bash
psql --version
# psql (PostgreSQL) 14.0 (Debian 14.0-1.pgdg110+1)

psql -U postgres

DDL

適当にusersテーブルとusers_historyテーブルを作って、usersの行が更新されたら、変更前後のusers.nameusers_historyに書き込むようなトリガーを作る。 write_user_history()の中に前述のRAISE文も書いておく。

CREATE TABLE users(
   id serial NOT NULL,
   name text,
   PRIMARY KEY (id)
);

CREATE TABLE users_history(
   id serial NOT NULL,
   user_id integer,
   old_name text,
   new_name text,
   PRIMARY KEY (id)
);


CREATE OR REPLACE FUNCTION write_user_history() RETURNS "trigger" AS
$$
DECLARE
BEGIN
  RAISE NOTICE 'messagea';
  insert into  users_history(
    user_id,
    new_name,
    old_name
  ) values (
    new.id,
    new.name,
    old.name
  );
  RETURN new;
END;
$$
LANGUAGE plpgsql VOLATILE;

CREATE TRIGGER write_user_historys_trigger
  AFTER UPDATE
  ON users
  FOR EACH ROW
  EXECUTE PROCEDURE write_user_history();

トリガーを動かしてみる

実際にトリガーを動かしてみる。 なお、トリガーを動かす前に、NOTICEレベルのログをクライアント(psql)とログに出力するための設定を行う。

ALTER SYSTEM SET client_min_messages = NOTICE;
ALTER SYSTEM SET log_min_messages = NOTICE;

usersテーブルに更新対象になるデータをINSERTします。

INSERT INTO users(name) values('hoge');

-- INSERT 0 1

SELECT * FROM users;

-- id | name
-- ----+------
--  1 | hoge
-- (1 row)

SELECT * FROM users_history;

-- id | user_id | old_name | new_name
-- ----+---------+----------+----------
-- (0 rows)

そのデータに対してEXPLAIN ANALYZEを利用したSQL文で更新をすると、前述の通り、実行計画とともにトリガーが実行されたことと、所要時間、実行回数が分かる。 また、この時点でRAISEしたメッセージもpsql上で確認することができる。

EXPLAIN ANALYZE UPDATE users SET name = 'fuga' where id = 1;

-- NOTICE:  messagea
 
--                                                      QUERY PLAN
-- 
-- -------------------------------------------------------------------------------------------------------------------------
--  Update on users  (cost=0.15..8.17 rows=0 width=0) (actual time=0.026..0.026 rows=0 loops=1)
--   ->  Index Scan using users_pkey on users  (cost=0.15..8.17 rows=1 width=38) (actual time=0.006..0.006 rows=1 loops=1)
--        Index Cond: (id = 1)
-- Planning Time: 0.083 ms
-- Trigger write_user_historys_trigger: time=0.414 calls=1
-- Execution Time: 0.456 ms
-- (6 rows)

users_historyテーブルにもちゃんとデータが入っている。

SELECT * FROM users;
SELECT * FROM users_history;

-- id | name
-- ----+------
--  1 | fuga
-- (1 row)

-- id | user_id | old_name | new_name
-- ----+---------+----------+----------
--   1 |       1 | hoge     | fuga
-- (1 row)

一旦コンテナから抜けて、docker logsを実行すると、RAISE文の結果がログにも出力されていることが確認できる。

docker logs example-postgres

# 2021-10-09 08:07:15.580 UTC [51] NOTICE:  messagea
# 2021-10-09 08:07:15.580 UTC [51] CONTEXT:  PL/pgSQL function write_user_history() line 4 at RAISE

わりと、PostgreSQLだけでもデバックに便利な機能はある、というのが発見だった。

他にもデバックという点でいうと

  • PL/pgSQLのASSERT文を使ってテストを書けそう
  • pgAdmin付属のデバッガ―を使ってブレークポイントを使ったりしてデバックできそう

ということもできるようだが、今回はいったんここまで。

*1:私はあまり複雑なトリガーを書いたことはないが、トランザクション自体を失敗にすることもできるので使いようはいろいろありそう

Windows 10 に開発環境を作る(WSL2 Docker編)

ようやくちょっと時間ができたので、自宅のWindows10に開発環境を準備しようとした作業メモ①です。

WSL2 で Ubunutu をインストールする

Windows 10 に WSL をインストールする | Microsoft Docsなどを参考にするとよい。

気を付けるべきこと

Ubuntuをインストールしたら、WSL2でインストールされていることを確認しておきましょう。後述しますが、昔使った環境を流用しようとしたらWSL1だったので、余計な時間を使いました。

> wsl --list --verbose
  NAME            STATE           VERSION
* Ubuntu-20.04    Stopped         2

Ubuntu をとりあえず最新化

sudo apt-get update
sudo apt-get upgrade

((そういえば、apt-getよりaptを使う方がよかったんだよなーとか実行してから気付いた)

Docker をインストール

とりあえず Ubuntu20.04 でapt searchしてみると、Ubuntu のレポジトリにはdocker.ioがあるので何も考えずapt installすることが可能。一方で公式の、Install Docker Engine on Ubuntu | Docker Documentationに従ってインストール方法もあるようだ。

  • 公式の方法で 既存の Ubuntu18.04 環境にインストールするも失敗
  • 20.04 に docker.io をインストールするも失敗

したので、結局、新しい Ubuntu20.04 環境に Docker 公式の方法でインストールすることにします。

sudo apt-get update
sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo \
  "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

docker の動作確認

デーモン起動 & デーモン起動確認 & Hello Worldをします。

$ sudo service docker start
 * Starting Docker: docker                                                                                       [ OK ]
$ sudo service docker status
 * Docker is running
$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:61bd3cb6014296e214ff4c6407a5a7e7092dfa8eefdbbec539e133e97f63e09f
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

ちゃんとHello Worldできました。よかった...

発生した問題

Docker デーモンが起動しない

Ubuntu18.04 において、Install Docker Engine on Ubuntu | Docker Documentationに従ってインストールを実施したところ、 Docker デーモンを起動しようとすると、一時的に起動しているようだがすぐ停止してしまう。

$ sudo service docker start
 * Starting Docker: docker                                                                                                        [ OK ]
$ sudo service docker status
 * Docker is running
$ sudo service docker status
 * Docker is not running

準備していた環境がだいぶ前に作ったUbuntu環境だったので、改めてUbuntu 20.04 LTSをインストールて、docker.ioを使ってみたり、改めて公式イメージを使ってみたが改善しない。

WSL 2でCannot connect to the Docker daemonと言われたら、デフォルトを確認する。 | Ginpen.comなどを確認すると、ちゃんと WSL2 が利用されているか確認した方がよさそうなので確認する。

> wsl --list --verbose
  NAME            STATE           VERSION
* Ubuntu-20.04    Running         1

...残念ながら WSL1 で動いていたようなので Version を変更する。うーん、無駄な時間を使ってしまった。

> wsl --set-default-version 2
この操作を正しく終了しました。

> wsl --set-version Ubuntu-20.04 2
変換中です。この処理には数分かかることがあります...
WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください
変換が完了しました。

> wsl --list --verbose
  NAME            STATE           VERSION
* Ubuntu-20.04    Stopped         2

こうするとDockerデーモンが普通に起動するようになりました。

docker.io をインストールしても、Docker デーモンを起動できない

docker.ioをインストールする例が余り日本語で見つけられなかったのだが、apt show docker.ioでみるとパッケージ 1 つで動作しそうだし、apt changelog docker.ioしてみてもそこそこ更新はされてるようだったので、docker.ioを使っても大丈夫だろうと判断して実行しようとした。*1

しかし、docker.ioを WSL2 上の Ubuntu20.04 インストールして、docker デーモンを起動しようとするが、そもそもサービスとしてインストールされておらず起動できない。sudo service --status-allを実行しても表示されない。

$ sudo service docker start
docker: unrecognized service

どうやらdocker.iosystemctlで操作することが前提のようなのだが、WSL2 ではSystemdが無効化されていて、sytemctlは使えない。つまりdocker.ioを WSL2 で使うのは困難そうなことが分かった。このため Docker 公式の方法でインストールすることにした。

WSL2 において、なぜ Sytemd が使えないか&無理やり使う方法はこちらなどを参照のこと。 WSL2 で Systemd を使うハック - Qiita

※この後、UbuntuがWSL1で動いていたことが分かったのだが、どちらにしろ原因は上記だと思っています。

*1:docker.io と公式の docker の違いは、ubuntu - What is docker.io in relation to docker-ce and docker-ee? - Stack Overflowあたりを参考。

OpenAPI3の`#/components/examples/`で定義したexampleは`'#/components/schemas/`で`$ref`できない

タイトルままなのだけれど、OpenAPI3の#/components/examples/で定義したexampleは'#/components/schemas/$refできません。 APIのレスポンスボディの例として使おうとして気づきました。

NGパターン

OpenAPIで、#/components/examples/に例を定義して、'#/components/schemas/で定義したスキーマオブジェクトで参照してみます。 以下のようなyaml

openapi: "3.0.3"
info:
  title: sample
  description: "`hoge"
  version: "1.0.0"
servers:
  - url: http://example.com/api
paths:
  /user:
    post:
      description: create user
      responses:
        '201':
          description: created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
components:
  schemas: 
    User:
      type: object 
      required: 
        - id
      properties:
        id: 
          type: integer 
          format: int64 
        name:
          type: string
      example:
        $ref: '#/components/examples/UserExample'
  examples:
    UserExample:
      value:
        id: 1
        name: John

このyamlをSwagger Editorで表示すると、エラーは出力されないものの、exampleの表示は$ref: '#/components/examples/UserExampleになって意図したオブジェクトが表示されません。 下記のようなイメージ。

exampleをうまく参照できていない
exampleをうまく参照できていない

スキーマのexample表示もなにかがおかしい

スキーマの例にもvalueが表示される
スキーマの例も何かがおかしい

OKパターン

色々試したんですが、APIのレスポンスボディとして#/components/examples/に定義した例を使いたい場合は、Media Type Objectexamplesフィールドで参照するとうまくいきます。

openapi: "3.0.3"
info:
  title: sample
  description: "`hoge"
  version: "1.0.0"
servers:
  - url: http://example.com/api
paths:
  /user:
    post:
      description: Returns all pets from the system that the user has access to
      responses:
        '201':
          description: create user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              examples:
                User:
                  $ref: '#/components/examples/UserExample'
components:
  schemas: 
    User:
      type: object 
      required: 
        - id
      properties:
        id: 
          type: integer 
          format: int64 
        name:
          type: string
      example:
        id: 2
        name: Bob
  examples:
    UserExample:
      value:
        id: 1
        name: John

上記のように書くと、以下のようにちゃんと表示してくれます。

exampleをうまく参照できている
exampleをうまく参照できている

それもそのはずで、OpenAPIの仕様をちゃんと読むと、exampleフィールドとexamplesフィールドって明確に区別されてるのです。

例えば、Schema Objectなどにあるexampleフィールドの型はAnyでフリーフォーマットな記載が可能なものです。

一方で、#/components/examples/はExample Object を定義しているので、このObjectが利用可能なフィールドでだけ参照することができます。

仕様をちゃんと読まずに、しょうもないことに時間を使ってしまいまいた。英語力が欲しい。

IFTTT と Micorosoft Power Automate を連携して Alexa から Microsoft TODO にタスク登録しようとしてうまくいかなかった記録

追記あり:結局、連携させることができました。

上手くいかなかったことも記録しておけば後々役に立つかもしれないので書いておきます。

やりたかったこと

Alexa 経由で音声で、Microsoft TODO にタスクを登録したい。

やろうとしたこと

Webhook で IFTTT をトリガーに Microsoft Flow を動かす - Qiitaを参考に以下をやってみようとした。

  • IFTTT
    • Alexaの”Item added to your To Do List”トリガーを利用して、AlexaのToDoリストへのタスクの追加を契機にアクションを実行
    • Webhooksの"Make a web Request"アクションを利用して、AlexaのToDoリストに追加されたタスクの名称をBodyにしつつ、Power AutomateのWebhookへリクエストを送る
  • Microsoft Power Automate
    • "HTTP要求の受信時”を使ってリクエストを受け付ける
    • "タスクの作成(プレビュー)"を使ってMicrosoft ToDoにリクエストの内容でタスクを作成する

できたこと

  • IFTTT
    • Alexaの”Item added to your To Do List”トリガーを利用して、AlexaのToDoリストへのタスクの追加を契機にアクションを実行
  • Microsoft Power Automate
    • "HTTP要求の受信時”を使ってリクエストを受け付ける
    • "タスクの作成(プレビュー)"を使ってMicrosoft ToDoにリクエストの内容でタスクを作成する
      • ローカルマシンからInvoke-WebRequestコマンドレットを利用してタスクを作成することができた
        • Invoke-WebRequest $URL -Method POST

できなかったこと

  • IFTTTでWebhooksの"Make a web Request"アクションを利用して、AlexaのToDoリストに追加されたタスクの名称をBodyにしつつ、Power AutomateのWebhookへリクエストを送ること
    • トリガーからserviceは起動できているが、serviceが失敗する
      • エラーメッセージは以下の通り
        • Action failure message: Unable to make web request. Your server returned a undefined
    • Power Automate側の実行履歴にログが残っていない

考えていること

  • Power Automate側の実行履歴にログが残っていないので、IFTTTから正しいURLでPower Automateを呼び出せていないものと思われる
  • IFTTTに設定しているURLと同じURLを使って、Invoke-WebRequestでリクエストしてPower Automateを実行できているので、URLの誤入力ではない
  • IFTTTに設定する際のエスケープが必要?と適当にやってみたがうまくいかない。
    • URL全体をクォートしてみる ⇒ そもそもIFTTTは引用符始まりのURLを受け付けてくれないのでNG
    • URLのクエリパラメータを<<<>>>で囲ってみる ⇒ エラーに変化はない
  • うーんよくわからないけど何か制限事項があるのか...?

代替手段

追記

日を置いて改めて設定を見直したら、BODYの指定を誤っていて(記号({)が全角文字だった)、それを直しただけで正常にPOSTできるようになりました。 とてもしょうもないのですが、連携できてよかった。

あとは余談ですが、Curlでアンパサンドを含むURLを送付するときはエスケープしないとBashの機能と競合するので注意しましょう。

自分向けのリンク集(開発手法まわり)

随時更新

自分向けのリンク集(開発手法まわり)

随時更新