Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8829

Webサービスのe2eテスト  〜メール認証編〜

$
0
0

はじめに

この記事は Goodpatch Advent Calendar 2019の22日目です.

私が現在担当しているWebサービスの開発において、Puppeteerを用いたe2eテストを用いてQAの効率化を図っています。
この記事では Node.js と Gmail API を使い、アカウント作成時のメール認証を自動化する方法について共有したいと思います。

注:この記事ではPuppeteerには触れません!

環境準備

メールをNode.jsで取得するためには、Gmail APIの設定と、各種ファイルの取得・生成が必要です。
基本的にはNode.js Quickstartに従って作業します。

フォルダの準備

あらかじめ、各種ファイルを保存するフォルダの準備しておきます。

例として、以下のような構造にします。

image.png

Gmail Credentialの取得と配置

あらかじめ、利用するGmailのアカウントでログインしておきます。
image.png

ログイン後、以下のページを開きます
Node.js Quickstart | Gmail API

ボタンを押して、APIをEnableにします
image.png

モーダルが表示されるので、ボタンを押してcredensial.jsonをダウンロードし、
image.png

e2e/envフォルダに保存します
image.png

トークンの取得

Node.js Quickstart | Gmail APIの Step2 に従って、以下をインストールします。

$ npm install googleapis@39 --save

Node.js Quickstart | Gmail API
中のStep 3 のコードをコピーし、 e2e/scripts/get-token.jsと名付けて保存します。

フォルダ構造を合わせるため、credentials.jsのファイルパスと、トークンの出力先パス TOKEN_PATHを以下のように修正します。

constTOKEN_PATH=__dirname+'/../env/token.json';// Load client secrets from a local file.fs.readFile(__dirname+'/../env/credentials.json',(err,content)=>{

保存後、以下のコマンドを実行します。

$ node e2e/scripts/get-token

すると、以下ようにURLがあらわれるので、言われた通りこのページを開きます。
image.png

ログインするアカウントを選択すると、以下の画面がでて来るので、詳細を表示Quickstartに移動します
image.png

権限の付与を許可します
image.png
image.png

コードが表示されるので、コピーし、
image.png

ターミナルに戻って Enter the code from that page here:のあとに貼り付け、Enterします。
image.png

すると、Gmailで利用しているラベルの一覧が表示され、e2e/envフォルダに token.jsファイルが生成されます。

これで、Node.jsからGmailを利用する準備は完了です。

Gmailから目的のメールを取得する

準備が長かった気がしますが、ここからが本番です。

数多くのメールの中から、認証リンクを含んだメールを探し、本文からリンク取得します。

取得すべきメール

取得すべきメールは以下のようなものです。
APIのフィルタ機能と、本文への正規表現を用いた検索を使ってこのメールを探します。

  • 未読状態である。
  • Webサービスのメール送信用アドレスから送信されている。
    (ここではhoge@piyo.jpとします)
  • 登録したメールアドレスに送信されている。
    (登録に利用したメールアドレスを引数として渡す)
  • 本文に認証リンクを含む。
    (ここでは https://hoge.piyo.jp/mail/XXXXXXのフォーマットであるとします)

環境準備

追加で以下のpackageをインストールします。

$ npm i google-auth-library -s

最終的なコード

先に最終的なコードを貼っておきます。
以下で説明していきます。

constfs=require('fs')const{promisify}=require('util')const{google}=require('googleapis')const{OAuth2Client}=require('google-auth-library')constgmail=google.gmail('v1')constTOKEN_PATH=__dirname+'/../env/token.json'constSECRET_PATH=__dirname+'/../env/credentials.json'constsleep=msec=>newPromise(resolve=>setTimeout(resolve,msec))constMAX_RETRY=10//Promise 化constreadFileAsync=promisify(fs.readFile)constgetMessageList=params=>{returnnewPromise((resolve,reject)=>{gmail.users.messages.list(params,(error,response)=>{if(error){reject(error)return}resolve(response.data)})})}constgetMessage=params=>{returnnewPromise((resolve,reject)=>{gmail.users.messages.get(params,(error,response)=>{if(error){reject(error)return}resolve(response.data)})})}constgetRegisterToken=async({email})=>{//クレデンシャル情報の取得constcontent=awaitreadFileAsync(SECRET_PATH)//クライアントシークレットのファイルを指定constcredentials=JSON.parse(content)//クレデンシャル//認証constclientSecret=credentials.installed.client_secretconstclientId=credentials.installed.client_idconstredirectUrl=credentials.installed.redirect_uris[0]constoauth2Client=newOAuth2Client(clientId,clientSecret,redirectUrl)consttoken=awaitreadFileAsync(TOKEN_PATH)oauth2Client.credentials=JSON.parse(token)//API経由でシートにアクセスtry{constgetToken=async()=>{// メッセージリスト取得constdata=awaitgetMessageList({auth:oauth2Client,userId:'me',q:`is:unread from:piyo@hoge.jp to:${email}`,})if(!data.messages||data.messages.length===0){console.log('no message')return}constmessage=awaitgetMessage({auth:oauth2Client,userId:'me',id:data.messages[0].id,})consttext=Buffer.from(message.payload.parts[1].body.data,'base64').toString('utf8')constregex=newRegExp(/https:\/\/hoge\.piyo\.jp\/mail\/([A-Za-z0-9]+)/)constresult=text.match(regex)if(!result){console.log('not matched')return}console.log('matched',result[1])returnresult[1]}letretry=MAX_RETRYlettoken=nullwhile(retry>0){token=awaitgetToken()if(token){break}retry--awaitsleep(10000)}console.log('token',token)returntoken}catch(err){return''}}module.exports={getRegisterToken}

Promise化

async/awaitで書きたいので、各種関数をPromiseでラップします。
promisifyが使えるものについては、promisifyを使い、そうでないものはベタに書いていきます。

//promisifyでプロミス化constreadFileAsync=promisify(fs.readFile)constgetMessageList=params=>{returnnewPromise((resolve,reject)=>{gmail.users.messages.list(params,(error,response)=>{if(error){reject(error)return}resolve(response.data)})})}constgetMessage=params=>{returnnewPromise((resolve,reject)=>{gmail.users.messages.get(params,(error,response)=>{if(error){reject(error)return}resolve(response.data)})})}

認証データの準備

認証データをファイルから読み出し、OAuth2の認証クライアントを生成します。

//クレデンシャル情報の取得constcontent=awaitreadFileAsync(SECRET_PATH)//クライアントシークレットのファイルを指定constcredentials=JSON.parse(content)//クレデンシャル//認証constclientSecret=credentials.installed.client_secretconstclientId=credentials.installed.client_idconstredirectUrl=credentials.installed.redirect_uris[0]constoauth2Client=newOAuth2Client(clientId,clientSecret,redirectUrl)consttoken=awaitreadFileAsync(TOKEN_PATH)oauth2Client.credentials=JSON.parse(token)

メッセージリストの取得

メッセージリストを検索クエリを付与してフィルタリングし、取得します。

指定意味
is:unread未読
from:hoge@piyo.jphoge@piyo.jpから送信されている
to:${email}${email} へ送信されている
// メッセージリスト取得constdata=awaitgetMessageList({auth:oauth2Client,userId:'me',q:`is:unread from:piyo@hoge.jp to:${email}`,})

メール本文の取得

メール本文を取得します。
ここでは、メッセージリストで複数候補があっても1つ目のメールのみを取得しています。

constmessage=awaitgetMessage({auth:oauth2Client,userId:'me',id:data.messages[0].id,//1つ目のメールを指定})

デコード

Gmailでは本文は、UTF-8の文字列バイトデータがBase64エンコードされたものになっています。
そのため、Base64からバイト配列に変換し、UTF-8に変換、といったデコードが必要です。

consttext=Buffer.from(message.payload.parts[1].body.data,'base64').toString('utf8')

文字列の抽出

ここまできたら正規表現で文字列を検索・抽出してあげるだけです!

constregex=newRegExp(/https:\/\/hoge\.piyo\.jp\/mail\/([A-Za-z0-9]+)/)constresult=text.match(regex)

リトライ

メールが直に送信されるとは限らないので、リトライの仕組みを入れています。
ここでは、10秒毎に最大10回リトライするようにしています。

letretry=MAX_RETRYlettoken=nullwhile(retry>0){token=awaitgetToken()if(token){break}retry--awaitsleep(10000)}

最後に

まだ追記するべき点がありますので、後ほど更新します!

時間切れにてここまで。何かのお役に立ちますように〜〜〜!


Viewing all articles
Browse latest Browse all 8829

Trending Articles