Linc’well Advent Calendar10日目の記事です。
当社が展開するクリニックグループの CLINIC FORの予約システム で、認証機能の一部にAWS Cognitoへの移行を進めております。
予約システム全体としてはモノリシックなRailsアプリケーションで開発をしてきたのですが、AWS CognitoをRailsアプリケーションの上に被すように実装する必要がり、連携をするためにAWS CognitoのTriggerをlambdaで実装する必要がでてきました。
この記事では、ローカル環境でのlambda開発、テストをどう構築したかをまとめます。
lambda 開発環境構築
今回は、Serverless Frameworkを使って開発を進めます。Frameworkのinstallとprojectを作成します。
npm install -g serverless
serverless create --template aws-nodejs --name cognito-triggers --path cognito-triggers
cd cognito-triggers
lambda の依存パッケージをインストール
RailsアプリケーションにAWS Cognitoを連携させるために、http clientとしてaxios
を使います。
npm init
npm install axios --save
lambda のコードを書く
このは、AWS Cognitoのサインアップ前 Lambda triggerです。AWS Cognitoは、自前のバックエンド等と連携するため、沢山のTriggerを提供しています。
ご興味あるかたは、こちらをご覧ください。
サインアップ前に、入力されたemailが、登録済 or 未登録 か判定して、登録済であればCognitoへ登録、未登録であればCognitoへ登録しない仕様の実装です。
'use strict';
const axios = require('axios');
async function exists(server, data, callback) {
let url = 'https://' + server + '/api/endpoint/exists';
const config = {
headers: {
"Content-Type": "application/json"
},
responseType: 'json'
};
await axios.get(url, {params: data}, config)
.then(response => {
console.log('response:', response.data);
callback(null, response.data);
})
.catch(function (error) {
callback(error);
});
}
module.exports.exec = async (event, context, callback) => {
console.log('start pre authentication user:', event.userName);
console.log(event)
let userAttributes = event['request']['userAttributes']
let email = userAttributes["email"];
let rails_server_url = process.env.API_HOST_NAME;
await exists(rails_server_url, {
email: email
}, (err, response) => {
if (err) {
return context.fail("Connection error");
}
// ユーザーの 登録済 or 未登録判定
if (response.user_exists == false) {
// 未登録
console.log('user not exists:', email);
context.succeed(event);
}
else {
// 登録済
console.log('user exists:', email);
context.fail("user exists");
}
});
};
lambda をテストする
Lambdaをテストするために、下記2つをmockする必要あります。
- AWS Cognitoのcontext
- axios
これらをmockしてテストします。
テストに必要なパッケージをインストール
テストフレームワークとして jest
、AWS Cognitoのmockとして aws-lambda-mock-context serverless-prune-plugin
もインストールします。
npm install jest aws-lambda-mock-context serverless-prune-plugin --save-dev
event data の準備
加えて、AWS CognitoがTriggerで送信してくるeventも準備する必要あります。
module.exports = {
testEvent: {
"userName": "test@example.com",
"request": {
"userAttributes": {
"email": "test@example.com"
}
}
}
};
テストコード
'use strict'
const { exec } = require('./pre-sign-up');
const event = require('./pre-sign-up-event-data');
// mock
const context = require('aws-lambda-mock-context');
const axios = require('axios');
jest.mock('axios');
describe('user-migration.js.exec()', () => {
describe('user_status: CONFIRM', () => {
describe('user not exists', () => {
it('return event', async () => {
// axios mock
axios.get.mockResolvedValue({
data: {user_exists: false}
});
const ctx = context();
await exec(event.testEvent, ctx, function () {
});
// AWS Cognito の context判定
await ctx.Promise
.then(result => {
expect(result).toBe(event.testEvent);
});
});
});
describe('user exists', () => {
it('through error', async () => {
// axios mock
axios.get.mockResolvedValue({
data: {user_exists: true}
});
const ctx = context();
await exec(event.testEvent, ctx, function () {
});
// AWS Cognito の context判定
await ctx.Promise
.catch(err => {
expect(err).not.toBeNull();
});
});
});
});
});
テスト実行
ローカル環境でテスト実行できるはずです。
npx jest
lambda の deploy
dev, stg, prd と deploy をしたいと考えると、
sls deploy -v --stage dev
こんな感じでdeployできると嬉しい。
stage別の設定ファイルを作成する
- environments/dev.yml
- environments/stg.yml
- environments/prd.yml
のように配置する。内容は下記の通り。
profile: your_aws_profile_name
region: aws_region_name
functionNamePrefix: function-prefix-
apiHostName: your-host-name.co.jp
serverless.yml の編集
sls deploy -v --stage dev
から環境依存部分を解決できるように変数を展開する。
service: cognito-triggers
provider:
name: aws
runtime: nodejs12.x
stage: ${opt:stage}
profile: ${self:custom.environments.${opt:stage}.profile}
region: ${self:custom.environments.${opt:stage}.region}
environment:
API_HOST_NAME: ${self:custom.environments.${opt:stage}.apiHostName}
plugins:
- serverless-prune-plugin
custom:
prune:
automatic: true
number: 5
environments:
dev: ${file(./environments/dev.yml)}
stg: ${file(./environments/stg.yml)}
prd: ${file(./environments/prd.yml)}
functions:
pre-sign-up:
name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger-pre-sign-up
handler: pre-sign-up.exec
user-migration:
name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--user-migration
handler: user-migration.exec
post-authentication:
name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--post-authentication
handler: post-authentication.exec
post-confirmation:
name: ${self:custom.environments.${opt:stage}.functionNamePrefix}cognito-trigger--post-confirmation
handler: post-confirmation.exec
CIrcleCI で継続的にtest実行するための設定
詳細は割愛しますが、↓のように、install, test 実行を追加。
:
steps:
- run:
name: install dependencies for lambda/cognito-triggers
command: npm install
working_directory: lambda/cognito-triggers
:
- run:
name: run tests for lambda/cognito-triggers
command: npm test
working_directory: lambda/cognito-triggers
:
まとめ
こんな感じで、ローカル環境での開発、テスト、テスト後のDeployまで一気通貫できました。さらに良いプラクティスがあればフィードバック頂けたら幸いです!