Merged PR 600: [ライセンスアラート改善]リトライ対応

## 概要
[Task3025: [ライセンスアラート改善]リトライ対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3025)

ライセンスアラート処理にリトライ処理を追加しました。
メールの多重送信を防ぐために、送信成功したメールについてはredisに保存し、送信時にキャッシュをチェックする処理を入れました。

## レビューポイント
処理の流れが妥当か。
redisに保存するキー、値は適切か。
if文のネストが相当深くなってしまったが、改善できるポイントはあるか。

## UIの変更
なし

## 動作確認状況
ローカルで動作確認済み。(テスト用コードで無理やりエラーを発生させての確認)

## 補足
なし
This commit is contained in:
oura.a 2023-12-06 00:46:53 +00:00
parent 94f34a0fde
commit 4399a61f2b
8 changed files with 500 additions and 172 deletions

View File

@ -11,5 +11,10 @@
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"retry": {
"strategy": "fixedDelay",
"maxRetryCount": 3,
"delayInterval": "00:00:10"
}
}
}

View File

@ -5,8 +5,8 @@ import { AdB2cResponse, AdB2cUser } from "./types/types";
import { error } from "console";
import { makeADB2CKey, restoreAdB2cID } from "../common/cache";
import { promisify } from "util";
import { createRedisClient } from "../redis/redis";
import { InvocationContext } from "@azure/functions";
import { RedisClient } from "redis";
export class Adb2cTooManyRequestsError extends Error {}
@ -23,16 +23,24 @@ export class AdB2cService {
) {
throw error;
}
const credential = new ClientSecretCredential(
process.env.ADB2C_TENANT_ID,
process.env.ADB2C_CLIENT_ID,
process.env.ADB2C_CLIENT_SECRET
);
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
scopes: ["https://graph.microsoft.com/.default"],
});
try {
const credential = new ClientSecretCredential(
process.env.ADB2C_TENANT_ID,
process.env.ADB2C_CLIENT_ID,
process.env.ADB2C_CLIENT_SECRET
);
const authProvider = new TokenCredentialAuthenticationProvider(
credential,
{
scopes: ["https://graph.microsoft.com/.default"],
}
);
this.graphClient = Client.initWithMiddleware({ authProvider });
this.graphClient = Client.initWithMiddleware({ authProvider });
} catch (error) {
console.log(error);
throw error;
}
}
/**
@ -42,9 +50,9 @@ export class AdB2cService {
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[] | undefined> {
const redisClient = createRedisClient();
): Promise<AdB2cUser[]> {
try {
const b2cUsers: AdB2cUser[] = [];
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
@ -123,7 +131,7 @@ export class AdB2cService {
return [...cachedUsers, ...b2cUsers];
} else {
return undefined;
return b2cUsers;
}
} catch (e) {
const { statusCode } = e;
@ -132,7 +140,6 @@ export class AdB2cService {
}
throw e;
} finally {
redisClient.quit;
}
}
}

View File

@ -1 +1,5 @@
export const ADB2C_PREFIX = "adb2c-external-id:"
export const ADB2C_PREFIX = "adb2c-external-id:";
export const SEND_COMPLETE_PREFIX = "send-complete-id:";
export const MAIL_U103 = "[U103]";
export const MAIL_U104 = "[U104]";
export const DONE = "Done"; // メール送信成功時にredisにキャッシュする値

View File

@ -1,4 +1,4 @@
import { ADB2C_PREFIX } from './constants';
import { ADB2C_PREFIX, SEND_COMPLETE_PREFIX } from "./constants";
/**
* ADB2Cのユーザー格納用のキーを生成する
@ -6,8 +6,8 @@ import { ADB2C_PREFIX } from './constants';
* @returns
*/
export const makeADB2CKey = (externalId: string): string => {
return `${ADB2C_PREFIX}${externalId}`;
}
return `${ADB2C_PREFIX}${externalId}`;
};
/**
* ADB2Cのユーザー格納用のキーから外部ユーザーIDを取得する
@ -15,5 +15,20 @@ export const makeADB2CKey = (externalId: string): string => {
* @returns ID
*/
export const restoreAdB2cID = (key: string): string => {
return key.replace(ADB2C_PREFIX, '');
}
return key.replace(ADB2C_PREFIX, "");
};
/**
*
* @param formattedDate YYYY:MM:DD
* @param externalId ID
* @param mail
* @returns
*/
export const makeSendCompKey = (
formattedDate: string,
externalId: string,
mail: string
): string => {
return `${SEND_COMPLETE_PREFIX}${formattedDate}${mail}${externalId}`;
};

View File

@ -19,36 +19,166 @@ import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
import { getMailFrom } from "../common/getEnv/getEnv";
import { createRedisClient } from "../redis/redis";
import { RedisClient } from "redis";
import { promisify } from "util";
import { makeSendCompKey } from "../common/cache";
import {
MAIL_U103,
MAIL_U104,
SEND_COMPLETE_PREFIX,
DONE,
} from "../common/cache/constants";
export async function licenseAlertProcessing(
context: InvocationContext,
datasource: DataSource,
redisClient: RedisClient,
sendgrid: SendGridService,
adb2c: AdB2cService
) {
context.log("[IN]licenseAlertProcessing");
const mailFrom = getMailFrom();
const accountRepository = datasource.getRepository(Account);
try {
context.log("[IN]licenseAlertProcessing");
// 第五のアカウントを取得
const accounts = await accountRepository.find({
where: {
tier: TIERS.TIER5,
},
relations: {
primaryAdminUser: true,
secondaryAdminUser: true,
},
});
// redisのキー用
const currentDate = new DateWithZeroTime();
const formattedDate = `${currentDate.getFullYear()}-${(
currentDate.getMonth() + 1
).toString()}-${currentDate.getDate().toString()}`;
const keysAsync = promisify(redisClient.keys).bind(redisClient);
const licenseRepository = datasource.getRepository(License);
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
const currentDateWithZeroTime = new DateWithZeroTime();
const currentDateWithDayEndTime = new DateWithDayEndTime();
const sendTargetAccounts = [] as accountInfo[];
// メール送信対象のアカウント情報を取得
const sendTargetAccounts = await getAlertMailTargetAccount(
context,
datasource
);
const counts = async () => {
// adb2cからメールアドレスを取得し、上記で取得したアカウントにマージする
const sendTargetAccountsMargedAdb2c = await createAccountInfo(
context,
redisClient,
adb2c,
sendTargetAccounts
);
// メール送信
await sendAlertMail(
context,
redisClient,
sendgrid,
sendTargetAccountsMargedAdb2c,
formattedDate
);
// 最後まで処理が正常に通ったら、redisに保存した送信情報を削除する
try {
const delAsync = promisify(redisClient.del).bind(redisClient);
const keys = await keysAsync(`${SEND_COMPLETE_PREFIX}${formattedDate}*`);
console.log(`delete terget:${keys}`);
if (keys.length > 0) {
const delResult = await delAsync(...keys);
console.log(`delete number:${delResult}`);
}
} catch (e) {
context.log("redis delete failed");
throw e;
}
} catch (e) {
throw e;
} finally {
context.log("[OUT]licenseAlertProcessing");
}
}
export async function licenseAlert(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
context.log("[IN]licenseAlert");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let datasource: DataSource;
try {
datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License],
});
await datasource.initialize();
} catch (e) {
context.log("database initialize failed.");
context.error(e);
throw e;
}
let redisClient: RedisClient;
try {
// redis接続
redisClient = createRedisClient();
} catch (e) {
context.log("redis client create failed.");
context.error(e);
throw e;
}
try {
const adb2c = new AdB2cService();
const sendgrid = new SendGridService();
await licenseAlertProcessing(
context,
datasource,
redisClient,
sendgrid,
adb2c
);
} catch (e) {
context.log("licenseAlertProcessing failed.");
context.error(e);
throw e;
} finally {
await datasource.destroy();
redisClient.quit;
context.log("[OUT]licenseAlert");
}
}
/**
*
* @param context
* @param datasource
* @returns accountInfo[]
*/
async function getAlertMailTargetAccount(
context: InvocationContext,
datasource: DataSource
): Promise<accountInfo[]> {
try {
context.log("[IN]getAlertMailTargetAccount");
const currentDate = new DateWithZeroTime();
const expiringSoonDate = new ExpirationThresholdDate(currentDate.getTime());
const currentDateWithZeroTime = new DateWithZeroTime();
const currentDateWithDayEndTime = new DateWithDayEndTime();
// 第五のアカウントを取得
const accountRepository = datasource.getRepository(Account);
const accounts = await accountRepository.find({
where: {
tier: TIERS.TIER5,
},
relations: {
primaryAdminUser: true,
secondaryAdminUser: true,
},
});
const sendTargetAccounts = [] as accountInfo[];
const licenseRepository = datasource.getRepository(License);
for (const account of accounts) {
// 有効期限がしきい値より未来または未設定で、割り当て可能なライセンス数の取得を行う
const allocatableLicenseWithMargin = await licenseRepository.count({
@ -109,6 +239,7 @@ export async function licenseAlertProcessing(
let primaryAdminExternalId: string | undefined;
let secondaryAdminExternalId: string | undefined;
let parentCompanyName: string | undefined;
if (shortage !== 0 || userCount !== 0) {
primaryAdminExternalId = account.primaryAdminUser
? account.primaryAdminUser.external_id
@ -143,12 +274,33 @@ export async function licenseAlertProcessing(
secondaryAdminEmail: undefined,
});
}
};
await counts();
return sendTargetAccounts;
} catch (e) {
context.error(e);
context.log("getAlertMailTargetAccount failed.");
throw e;
} finally {
context.log("[OUT]getAlertMailTargetAccount");
}
}
/**
* Azure AD B2Cからユーザ情報を取得し
* @param context
* @param redisClient
* @param adb2c
* @param sendTargetAccounts RDBから取得したアカウント情報
* @returns accountInfo[]
*/
async function createAccountInfo(
context: InvocationContext,
redisClient: RedisClient,
adb2c: AdB2cService,
sendTargetAccounts: accountInfo[]
): Promise<accountInfo[]> {
// ADB2Cからユーザーを取得する用の外部ID配列を作成
const externalIds = [] as string[];
sendTargetAccounts.map((x) => {
sendTargetAccounts.forEach((x) => {
if (x.primaryAdminExternalId) {
externalIds.push(x.primaryAdminExternalId);
}
@ -156,11 +308,10 @@ export async function licenseAlertProcessing(
externalIds.push(x.secondaryAdminExternalId);
}
});
const adb2cUsers = await adb2c.getUsers(context, externalIds);
if (!adb2cUsers) {
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
if (adb2cUsers.length === 0) {
context.log("Target user not found");
context.log("[OUT]licenseAlertProcessing");
return;
return [];
}
// ADB2Cから取得したメールアドレスをRDBから取得した情報にマージ
sendTargetAccounts.map((info) => {
@ -188,17 +339,54 @@ export async function licenseAlertProcessing(
}
}
});
return sendTargetAccounts;
}
/**
*
* @param context
* @param redisClient
* @param sendgrid
* @param sendTargetAccounts
* @param formattedDate redisのキーに使用する日付
* @returns
*/
async function sendAlertMail(
context: InvocationContext,
redisClient: RedisClient,
sendgrid: SendGridService,
sendTargetAccounts: accountInfo[],
formattedDate: string
): Promise<void> {
try {
context.log("[IN]sendAlertMail");
// redis用
const getAsync = promisify(redisClient.get).bind(redisClient);
const setexAsync = promisify(redisClient.setex).bind(redisClient);
const ttl = process.env.ADB2C_CACHE_TTL;
const mailFrom = getMailFrom();
const sendMail = async () => {
for (const targetAccount of sendTargetAccounts) {
// プライマリ管理者が入っているかチェック
// 入っていない場合は、アラートメールを送信する必要が無いため、何も処理をせず次のループへ
if (targetAccount.primaryAdminExternalId) {
// メール送信
// strictNullChecks対応
if (targetAccount.primaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
if (!targetAccount.primaryAdminEmail) {
continue;
}
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
// redisに送信履歴がない場合のみ送信する
const mailResult = await getAsync(
makeSendCompKey(
formattedDate,
targetAccount.primaryAdminExternalId,
MAIL_U103
)
);
if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseShortage(
targetAccount.companyName,
@ -217,45 +405,107 @@ export async function licenseAlertProcessing(
context.log(
`Shortage mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
} catch {
// 送信成功時、成功履歴をredisに保存
try {
const key = makeSendCompKey(
formattedDate,
targetAccount.primaryAdminExternalId,
MAIL_U103
);
await setexAsync(key, ttl, DONE);
context.log(
"setex Result:",
`key:${key},ttl:${ttl},value:Done`
);
} catch (e) {
context.error(e);
context.log(
"setex failed.",
`target: ${targetAccount.primaryAdminEmail}`
);
}
} catch (e) {
context.error(e);
context.log(
`Shortage mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
}
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (targetAccount.secondaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
);
} catch {
context.log(
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
);
}
}
throw e;
}
}
// ライセンス失効警告メール
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (
targetAccount.secondaryAdminEmail &&
targetAccount.secondaryAdminExternalId
) {
// redisに送信履歴がない場合のみ送信する
const mailResult = await getAsync(
makeSendCompKey(
formattedDate,
targetAccount.secondaryAdminExternalId,
MAIL_U103
)
);
if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Shortage mail send success. mail to :${targetAccount.secondaryAdminEmail}`
);
// 送信成功時、成功履歴をredisに保存
try {
const key = makeSendCompKey(
formattedDate,
targetAccount.secondaryAdminExternalId,
MAIL_U103
);
await setexAsync(key, ttl, DONE);
context.log(
"setex Result:",
`key:${key},ttl:${ttl},value:Done`
);
} catch (e) {
context.error(e);
context.log(
"setex failed.",
`target: ${targetAccount.secondaryAdminEmail}`
);
}
} catch (e) {
context.error(e);
context.log(
`Shortage mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
);
throw e;
}
}
}
}
// ライセンス失効警告メール
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
// redisに送信履歴がない場合のみ送信する
const mailResult = await getAsync(
makeSendCompKey(
formattedDate,
targetAccount.primaryAdminExternalId,
MAIL_U104
)
);
if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
@ -274,80 +524,99 @@ export async function licenseAlertProcessing(
context.log(
`Expiring soon mail send success. mail to :${targetAccount.primaryAdminEmail}`
);
} catch {
// 送信成功時、成功履歴をredisに保存
try {
const key = makeSendCompKey(
formattedDate,
targetAccount.primaryAdminExternalId,
MAIL_U104
);
await setexAsync(key, ttl, DONE);
context.log(
"setex Result:",
`key:${key},ttl:${ttl},value:Done`
);
} catch (e) {
context.error(e);
context.log(
"setex failed.",
`target: ${targetAccount.primaryAdminEmail}`
);
}
} catch (e) {
context.error(e);
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.primaryAdminEmail}`
);
throw e;
}
}
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (targetAccount.secondaryAdminEmail) {
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
// セカンダリ管理者が存在する場合、セカンダリ管理者にも送信
if (
targetAccount.secondaryAdminEmail &&
targetAccount.secondaryAdminExternalId
) {
// redisに送信履歴がない場合のみ送信する
const mailResult = makeSendCompKey(
formattedDate,
targetAccount.secondaryAdminExternalId,
MAIL_U104
);
if (mailResult !== DONE) {
const { subject, text, html } =
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
);
context.log(
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
);
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
mailFrom,
subject,
text,
html
const key = makeSendCompKey(
formattedDate,
targetAccount.secondaryAdminExternalId,
MAIL_U104
);
await setexAsync(key, ttl, DONE);
context.log(
`Expiring soon mail send success. mail to :${targetAccount.secondaryAdminEmail}`
"setex Result:",
`key:${key},ttl:${ttl},value:Done`
);
} catch {
} catch (e) {
context.error(e);
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
"setex failed.",
`target: ${targetAccount.secondaryAdminEmail}`
);
}
} catch (e) {
context.error(e);
context.log(
`Expiring soon mail send failed. mail to :${targetAccount.secondaryAdminEmail}`
);
throw e;
}
}
}
}
}
}
};
await sendMail();
context.log("[OUT]licenseAlertProcessing");
}
export async function licenseAlert(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
context.log("[IN]licenseAlert");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [User, Account, License],
});
await datasource.initialize();
const adb2c = new AdB2cService();
const sendgrid = new SendGridService();
try {
await licenseAlertProcessing(context, datasource, sendgrid, adb2c);
} catch (e) {
context.log("licenseAlertProcessing failed");
context.error(e);
context.log("sendAlertMail failed.");
throw e;
} finally {
await datasource.destroy();
context.log("[OUT]licenseAlert");
context.log("[OUT]sendAlertMail");
}
}

View File

@ -1,29 +0,0 @@
import { app, InvocationContext, Timer } from "@azure/functions";
import * as dotenv from "dotenv";
import { promisify } from "util";
import { createRedisClient } from "../redis/redis";
export async function redisTimerTest(
myTimer: Timer,
context: InvocationContext
): Promise<void> {
context.log("---Timer function processed request.");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const redisClient = createRedisClient();
const setAsync = promisify(redisClient.set).bind(redisClient);
const getAsync = promisify(redisClient.get).bind(redisClient);
await setAsync("foo", "bar");
const value = await getAsync("foo");
context.log(`value=${value}`); // returns 'bar'
await redisClient.quit;
}
app.timer("redisTimerTest", {
schedule: "*/30 * * * * *",
handler: redisTimerTest,
});

View File

@ -20,12 +20,34 @@ export const createRedisClient = (): RedisClient => {
host: host,
port: port,
password: password,
retry_strategy: (options) => {
if (options.attempt <= 3) {
console.log(
`Retrying connection to Redis. Attempt ${options.attempt}`
);
return 10000; // ミリ秒単位でのリトライまでの間隔
} else {
console.log("Exceeded maximum number of connection attempts.");
return undefined; // リトライを終了
}
},
});
} else {
client = createClient({
url: `rediss://${host}:${port}`,
password: password,
tls: {},
retry_strategy: (options) => {
if (options.attempt <= 3) {
console.log(
`Retrying connection to Redis. Attempt ${options.attempt}`
);
return 10000; // ミリ秒単位でのリトライまでの間隔
} else {
console.log("Exceeded maximum number of connection attempts.");
return undefined; // リトライを終了
}
},
});
}

View File

@ -13,6 +13,8 @@ import { ADB2C_SIGN_IN_TYPE } from "../constants";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { InvocationContext } from "@azure/functions";
import { RedisClient } from "redis";
import { createRedisClient } from "../redis/redis";
describe("licenseAlert", () => {
dotenv.config({ path: ".env" });
@ -40,6 +42,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@ -63,8 +66,15 @@ describe("licenseAlert", () => {
null
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
await licenseAlertProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
expect(spySend.mock.calls).toHaveLength(1);
redisClient.quit;
});
it("ライセンス在庫不足メール、ライセンス失効警告メールが送信されること", async () => {
@ -72,6 +82,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@ -96,8 +107,15 @@ describe("licenseAlert", () => {
null
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
await licenseAlertProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
expect(spySend.mock.calls).toHaveLength(2);
redisClient.quit;
});
it("在庫があるため、ライセンス在庫不足メールが送信されないこと", async () => {
@ -105,6 +123,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@ -142,8 +161,15 @@ describe("licenseAlert", () => {
null
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
await licenseAlertProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
expect(spySend.mock.calls).toHaveLength(0);
redisClient.quit;
});
it("AutoRenewがtureのため、ライセンス失効警告メールが送信されないこと", async () => {
@ -151,6 +177,7 @@ describe("licenseAlert", () => {
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
const redisClient = createRedisClient();
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
@ -175,8 +202,15 @@ describe("licenseAlert", () => {
null
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
await licenseAlertProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
expect(spySend.mock.calls).toHaveLength(1);
redisClient.quit;
});
});
@ -211,6 +245,7 @@ export class AdB2cServiceMock {
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[]> {
const AdB2cMockUsers: AdB2cUser[] = [