Merged PR 883: Functions修正

## 概要
[Task4132: Functions修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4132)

- 自動割り当てを行うライセンスの取得条件を変更
  - 有効期限が近いライセンスまたは有効期限が設定されていないライセンス(新規ライセンス)を取得する
  - 有効期限が近いものから割り当てを行うので、ソートはサーバー側で行うようにした
- メール送信処理を追加

## レビューポイント
- テンプレート取得からメール送信までの実装で漏れはないか
- テストケースは足りているか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- 2行目で割り当てるライセンスを取得しているが、その条件を修正した
- https://ndstokyo.sharepoint.com/:u:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/task4132/after.log?csf=1&web=1&e=jh49c3

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 追加したメール送信処理を確認するように各テストを修正し、テストが通っている
  - テストの観点を拡充したうえでテストが通っていることを確認
    - ライセンス割り当て履歴の内容をより詳細に確認するようにした

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2024-04-25 08:56:03 +00:00
parent ba7196cac1
commit af0ba78ae9
15 changed files with 1369 additions and 176 deletions

View File

@ -293,3 +293,13 @@ export const HTTP_STATUS_CODES = {
BAD_REQUEST: 400,
INTERNAL_SERVER_ERROR: 500,
};
/**
*
* @const {string}
*/
export const CUSTOMER_NAME = "$CUSTOMER_NAME$";
export const DEALER_NAME = "$DEALER_NAME$";
export const TOP_URL = "$TOP_URL$";
export const USER_NAME = "$USER_NAME$";
export const USER_EMAIL = "$USER_EMAIL$";

View File

@ -404,7 +404,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -464,7 +466,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
context,
[targetAccount.secondaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -523,7 +527,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -581,7 +587,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
context,
[targetAccount.secondaryAdminEmail],
[],
mailFrom,
subject,
text,

View File

@ -1,14 +1,20 @@
import { app, InvocationContext, Timer } from "@azure/functions";
import { Between, DataSource, In, MoreThan, Repository } from "typeorm";
import { Between, DataSource, In, IsNull, MoreThan, Repository } from "typeorm";
import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import * as dotenv from "dotenv";
import {
ADB2C_SIGN_IN_TYPE,
CUSTOMER_NAME,
DEALER_NAME,
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
SWITCH_FROM_TYPE,
TIERS,
TOP_URL,
USER_EMAIL,
USER_NAME,
USER_ROLES,
} from "../constants";
import {
@ -16,10 +22,19 @@ import {
DateWithZeroTime,
NewAllocatedLicenseExpirationDate,
} from "../common/types/types";
import { readFileSync } from "fs";
import path from "path";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { RedisClient } from "redis";
import { createRedisClient } from "../redis/redis";
export async function licenseAutoAllocationProcessing(
context: InvocationContext,
datasource: DataSource,
redisClient: RedisClient,
sendGrid: SendGridService,
adb2c: AdB2cService,
dateToTrigger?: Date
): Promise<void> {
try {
@ -32,6 +47,7 @@ export async function licenseAutoAllocationProcessing(
currentDateZeroTime = new DateWithZeroTime(dateToTrigger);
currentDateEndTime = new DateWithDayEndTime(dateToTrigger);
}
// 自動更新対象の候補となるアカウントを取得
const accountRepository = datasource.getRepository(Account);
const targetAccounts = await accountRepository.find({
@ -55,6 +71,9 @@ export async function licenseAutoAllocationProcessing(
await allocateLicense(
context,
datasource,
redisClient,
sendGrid,
adb2c,
autoAllocationList,
currentDateZeroTime,
currentDateEndTime
@ -94,8 +113,25 @@ export async function licenseAutoAllocation(
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;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(context, datasource);
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c
);
} catch (e) {
context.log("licenseAutoAllocation failed.");
context.error(e);
@ -215,24 +251,45 @@ export async function getAutoAllocatableLicense(
try {
context.log("[IN]getAutoAllocatableLicense");
// 割り当て可能なライセンスを取得
const license = await licenseRepository.findOne({
where: {
const license = await licenseRepository.find({
where: [
{
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime) || null,
expiry_date: MoreThan(currentDateEndTime),
},
order: {
expiry_date: "ASC",
{
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: IsNull(),
},
],
});
if (!license) {
if (license.length === 0) {
// 割り当て可能なライセンスが存在しない場合でもエラーとはしたくないので、undifinedを返却する
return undefined;
}
return license;
// ライセンスをソートする
// 有効期限が近いものから割り当てるため、expiry_dateがnullのものは最後にする
const sortedLicense = license.sort((a, b) => {
if (a.expiry_date && b.expiry_date) {
return a.expiry_date.getTime() - b.expiry_date.getTime();
} else if (a.expiry_date && !b.expiry_date) {
return -1;
} else if (!a.expiry_date && b.expiry_date) {
return 1;
} else {
return 0;
}
});
// 有効期限が近いライセンスを返却する
return sortedLicense[0];
} catch (e) {
context.error(e);
context.log("getAutoAllocatableLicense failed.");
@ -253,17 +310,23 @@ export async function getAutoAllocatableLicense(
export async function allocateLicense(
context: InvocationContext,
datasource: DataSource,
redisClient: RedisClient,
sendGrid: SendGridService,
adb2c: AdB2cService,
autoAllocationList: autoAllocationList,
currentDateZeroTime: DateWithZeroTime,
currentDateEndTime: DateWithDayEndTime
): Promise<void> {
try {
context.log("[IN]allocateLicense");
// 自動更新対象ユーザーにライセンスを割り当てる
try {
// 割り当て可能なライセンスが存在するかどうかのフラグ
let hasAllocatebleLicense = true;
// ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグ
let hasAutoRenewLicense = true;
for (const userId of autoAllocationList.userIds) {
await datasource.transaction(async (entityManager) => {
// フラグの初期化
hasAutoRenewLicense = true;
const licenseRepository = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory
@ -292,6 +355,7 @@ export async function allocateLicense(
});
if (!allocatedLicense) {
context.log(`skip auto allocation. userID:${userId}`);
hasAutoRenewLicense = false;
return;
}
@ -365,17 +429,276 @@ export async function allocateLicense(
if (!hasAllocatebleLicense) {
break;
}
// ユーザーに割り当てられているライセンスが自動更新対象であるかどうかのフラグがfalseの場合、次のユーザーへ
if (!hasAutoRenewLicense) {
continue;
}
try {
//メール送信に必要な情報をDBから取得
const userRepository = datasource.getRepository(User);
const accountRepository = datasource.getRepository(Account);
// ライセンスを割り当てたユーザーとアカウントの情報を取得
const user = await userRepository.findOne({
where: { id: userId },
});
if (!user) {
throw new Error(`Target user not found. ${userId}`);
}
const account = await accountRepository.findOne({
where: { id: autoAllocationList.accountId },
relations: {
primaryAdminUser: true,
secondaryAdminUser: true,
},
});
if (!account) {
throw new Error(
`Target account not found. ${autoAllocationList.accountId}`
);
}
// アカウントのプライマリー管理者が存在しない場合はエラー
if (!account.primaryAdminUser) {
throw new Error(
`Primary admin user not found. accountID: ${account.id}`
);
}
// 親アカウントが存在する場合は取得
let parentAccount: Account | null = null;
if (account.parent_account_id) {
parentAccount = await accountRepository.findOne({
where: { id: account.parent_account_id },
});
if (!parentAccount) {
throw new Error(
`Parent account not found. accountID: ${account.parent_account_id}`
);
}
}
// アカウントの管理者とライセンスを割り当てたユーザーのメールアドレス取得に必要な外部IDを抽出
const externalIds: string[] = [];
externalIds.push(user.external_id);
externalIds.push(account.primaryAdminUser.external_id);
// セカンダリ管理者が存在する場合はセカンダリ管理者の外部IDも抽出
if (account.secondaryAdminUser) {
externalIds.push(account.secondaryAdminUser.external_id);
}
const adb2cUsers = await getMailAddressAndDisplayNameList(
context,
redisClient,
adb2c,
externalIds
);
// ライセンス割り当てされたユーザーの名前を取得
const userName = adb2cUsers.find(
(adb2cUser) => adb2cUser.externalId === user.external_id
)?.displayName;
if (!userName) {
throw new Error(
`Target ADb2Cuser name not found. externalId=${user.external_id}`
);
}
// ライセンス割り当てされたユーザーのメールアドレスを取得
const userMail = adb2cUsers.find(
(adb2cUser) => adb2cUser.externalId === user.external_id
)?.mailAddress;
if (!userMail) {
throw new Error(
`Target ADb2Cuser mail not found. externalId=${user.external_id}`
);
}
// アカウントのプライマリー管理者のメールアドレスを取得
const adminMails: string[] = [];
const primaryAdminMail = adb2cUsers.find(
(adb2cUser) =>
adb2cUser.externalId === account.primaryAdminUser?.external_id
)?.mailAddress;
if (!primaryAdminMail) {
throw new Error(
`Primary admin user mail not found. externalId=${account.primaryAdminUser.external_id}`
);
}
adminMails.push(primaryAdminMail);
// アカウントのセカンダリ管理者のメールアドレスを取得
const secondaryAdminMail = adb2cUsers.find(
(adb2cUser) =>
adb2cUser.externalId === account.secondaryAdminUser?.external_id
)?.mailAddress;
if (secondaryAdminMail) {
adminMails.push(secondaryAdminMail);
}
// メール送信
await sendMailWithU108(
context,
userName,
userMail,
adminMails,
account.company_name,
parentAccount ? parentAccount.company_name : null,
sendGrid
);
} catch (e) {
context.error(`error=${e}`);
// メール送信に関する例外はログだけ出して握りつぶす
}
}
} catch (e) {
// エラーが発生しても次のアカウントへの処理は継続させるため、例外をthrowせずにreturnだけする
context.error(e);
context.log("allocateLicense failed.");
context.error(e);
return;
} finally {
context.log("[OUT]allocateLicense");
}
}
// adb2cから指定した外部IDのユーザー情報を取得する
export async function getMailAddressAndDisplayNameList(
context: InvocationContext,
redisClient: RedisClient,
adb2c: AdB2cService,
externalIds: string[]
): Promise<
{
externalId: string;
displayName: string;
mailAddress: string;
}[]
> {
context.log("[IN]getUsers");
try {
const users = [] as {
externalId: string;
displayName: string;
mailAddress: string;
}[];
// 外部IDからADB2Cユーザー情報を取得
const adb2cUsers = await adb2c.getUsers(context, redisClient, externalIds);
for (const externalId of externalIds) {
const adb2cUser = adb2cUsers.find((user) => user.id === externalId);
if (!adb2cUser) {
throw new Error(`ADB2C user not found. externalId=${externalId}`);
}
const mailAddress = adb2cUser.identities?.find(
(identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS
)?.issuerAssignedId;
if (!mailAddress) {
throw new Error(`ADB2C user mail not found. externalId=${externalId}`);
}
users.push({
externalId: externalId,
displayName: adb2cUser.displayName,
mailAddress: mailAddress,
});
}
return users;
} catch (e) {
context.error(e);
context.log("getUsers failed.");
throw e;
} finally {
context.log("[OUT]getUsers");
}
}
/**
* U-108使
* @param context
* @param userName
* @param userMail
* @param customerAdminMails (primary/secondary)
* @param customerAccountName
* @param dealerAccountName
* @returns mail with u108
*/
export async function sendMailWithU108(
context: InvocationContext,
userName: string,
userMail: string,
customerAdminMails: string[],
customerAccountName: string,
dealerAccountName: string | null,
sendGrid: SendGridService
): Promise<void> {
context.log("[IN] sendMailWithU108");
try {
const subject = "License Assigned Notification [U-108]";
const domain = process.env.APP_DOMAIN;
if (!domain) {
throw new Error("APP_DOMAIN is not defined.");
}
const mailFrom = process.env.MAIL_FROM;
if (!mailFrom) {
throw new Error("MAIL_FROM is not defined.");
}
const url = new URL(domain).href;
let html: string;
let text: string;
if (dealerAccountName === null) {
const templateU108NoParentHtml = readFileSync(
path.resolve(__dirname, `../templates/template_U_108_no_parent.html`),
"utf-8"
);
const templateU108NoParentText = readFileSync(
path.resolve(__dirname, `../templates/template_U_108_no_parent.txt`),
"utf-8"
);
html = templateU108NoParentHtml
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
text = templateU108NoParentText
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
} else {
const templateU108Html = readFileSync(
path.resolve(__dirname, `../templates/template_U_108.html`),
"utf-8"
);
const templateU108Text = readFileSync(
path.resolve(__dirname, `../templates/template_U_108.txt`),
"utf-8"
);
html = templateU108Html
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
text = templateU108Text
.replaceAll(CUSTOMER_NAME, customerAccountName)
.replaceAll(DEALER_NAME, dealerAccountName)
.replaceAll(USER_NAME, userName)
.replaceAll(USER_EMAIL, userMail)
.replaceAll(TOP_URL, url);
}
const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail];
// メールを送信する
await sendGrid.sendMail(
context,
customerAdminMails,
ccAddress,
mailFrom,
subject,
text,
html
);
} finally {
context.log(`[OUT] sendMailWithU108`);
}
}
app.timer("licenseAutoAllocation", {
schedule: "0 0 16 * * *",
handler: licenseAutoAllocation,

View File

@ -12,6 +12,10 @@ import { User } from "../entity/user.entity";
import { Account } from "../entity/account.entity";
import { License, LicenseAllocationHistory } from "../entity/license.entity";
import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants";
import { RedisClient } from "redis";
import { createRedisClient } from "../redis/redis";
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
export async function licenseAutoAllocationManualRetry(
req: HttpRequest,
@ -57,8 +61,27 @@ export async function licenseAutoAllocationManualRetry(
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;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(context, datasource, dateToTrigger);
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c,
dateToTrigger
);
context.log("Automatic license allocation has been triggered.");
return {
status: HTTP_STATUS_CODES.OK,

View File

@ -1,3 +1,4 @@
import { InvocationContext } from "@azure/functions";
import sendgrid from "@sendgrid/mail";
import { error } from "console";
@ -11,7 +12,9 @@ export class SendGridService {
/**
*
* @param context
* @param to
* @param cc
* @param from
* @param subject
* @param text
@ -19,28 +22,40 @@ export class SendGridService {
* @returns mail
*/
async sendMail(
to: string,
context: InvocationContext,
to: string[],
cc: string[],
from: string,
subject: string,
text: string,
html: string
html: string,
): Promise<void> {
context.log(`[IN] ${this.sendMail.name}`);
try {
const res = await sendgrid
.send({
from: {
email: from,
},
to: {
email: to,
},
to: to.map((v) => ({ email: v })),
cc: cc.map((v) => ({ email: v })),
subject: subject,
text: text,
html: html,
})
.then((v) => v[0]);
context.log(
` status code: ${
res.statusCode
} body: ${JSON.stringify(res.body)}`,
);
} catch (e) {
context.warn(`send mail faild.`);
context.warn(`${this.sendMail.name} error=${e}`);
throw e;
} finally {
context.log(`[OUT] ${this.sendMail.name}`);
}
}
}

View File

@ -0,0 +1,81 @@
<html>
<head>
<title>License Assigned Notification [U-108]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
Please be informed that a license has been assigned to the following
user.<br />
- User Name: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Please log in to ODMS Cloud to verify the license expiration date.<br />
URL: $TOP_URL$
</p>
<p>
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen
wurde.<br />
- Nutzername: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz
zu überprüfen.<br />
URL: $TOP_URL$
</p>
<p>
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich
bitte an $DEALER_NAME$.
</p>
<p>
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese
E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte
E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie
nicht.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
Veuillez être informé qu'une licence a été attribuée à l'utilisateur
suivant.<br />
- Nom d'utilisateur: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration
de la licence.<br />
URL: $TOP_URL$
</p>
<p>
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez
contacter $DEALER_NAME$.
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,47 @@
<English>
Dear $CUSTOMER_NAME$,
Please be informed that a license has been assigned to the following user.
- User Name: $USER_NAME$
- Email: $USER_EMAIL$
Please log in to ODMS Cloud to verify the license expiration date.
URL: $TOP_URL$
If you need support regarding ODMS Cloud, please contact $DEALER_NAME$.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde.
- Nutzername: $USER_NAME$
- Email: $USER_EMAIL$
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen.
URL: $TOP_URL$
Wenn Sie Unterstützung bezüglich ODMS Cloud benötigen, wenden Sie sich bitte an $DEALER_NAME$.
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie nicht.
<Français>
Chère/Cher $CUSTOMER_NAME$,
Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant.
- Nom d'utilisateur: $USER_NAME$
- Email: $USER_EMAIL$
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence.
URL: $TOP_URL$
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter $DEALER_NAME$.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -0,0 +1,70 @@
<html>
<head>
<title>License Assigned Notification [U-108]</title>
</head>
<body>
<div>
<h3>&lt;English&gt;</h3>
<p>Dear $CUSTOMER_NAME$,</p>
<p>
Please be informed that a license has been assigned to the following
user.<br />
- User Name: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Please log in to ODMS Cloud to verify the license expiration date.<br />
URL: $TOP_URL$
</p>
<p>
If you have received this e-mail in error, please delete this e-mail
from your system.<br />
This is an automatically generated e-mail and this mailbox is not
monitored. Please do not reply.
</p>
</div>
<div>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) $CUSTOMER_NAME$,</p>
<p>
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen
wurde.<br />
- Nutzername: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz
zu überprüfen.<br />
URL: $TOP_URL$
</p>
<p>
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese
E-Mail bitte aus Ihrem System.<br />
Dies ist eine automatisch generierte
E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie
nicht.
</p>
</div>
<div>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher $CUSTOMER_NAME$,</p>
<p>
Veuillez être informé qu'une licence a été attribuée à l'utilisateur
suivant.<br />
- Nom d'utilisateur: $USER_NAME$<br />
- Email: $USER_EMAIL$
</p>
<p>
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration
de la licence.<br />
URL: $TOP_URL$
</p>
<p>
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail
de votre système.<br />
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres
n'est pas surveillée. Merci de ne pas répondre.
</p>
</div>
</body>
</html>

View File

@ -0,0 +1,41 @@
<English>
Dear $CUSTOMER_NAME$,
Please be informed that a license has been assigned to the following user.
- User Name: $USER_NAME$
- Email: $USER_EMAIL$
Please log in to ODMS Cloud to verify the license expiration date.
URL: $TOP_URL$
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) $CUSTOMER_NAME$,
Bitte beachten Sie, dass dem folgenden Benutzer eine Lizenz zugewiesen wurde.
- Nutzername: $USER_NAME$
- Email: $USER_EMAIL$
Bitte melden Sie sich bei ODMS Cloud an, um das Ablaufdatum der Lizenz zu überprüfen.
URL: $TOP_URL$
Wenn Sie diese E-Mail irrtümlich erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und diese Mailbox wird nicht überwacht. Bitte antworten Sie nicht.
<Français>
Chère/Cher $CUSTOMER_NAME$,
Veuillez être informé qu'une licence a été attribuée à l'utilisateur suivant.
- Nom d'utilisateur: $USER_NAME$
- Email: $USER_EMAIL$
Veuillez vous connecter à ODMS Cloud pour vérifier la date d'expiration de la licence.
URL: $TOP_URL$
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.

View File

@ -0,0 +1,88 @@
import { InvocationContext } from "@azure/functions";
import { AdB2cUser } from "../../adb2c/types/types";
import { ADB2C_SIGN_IN_TYPE } from "../../constants";
import { RedisClient } from "redis-mock";
// テスト用adb2c
export class AdB2cServiceMock {
/**
* Azure AD B2Cからユーザ情報を取得する
* @param externalIds ID
* @returns
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[]> {
const AdB2cMockUsers: AdB2cUser[] = [
{
id: "external_id1",
displayName: "test1",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test1@mail.com",
},
],
},
{
id: "external_id2",
displayName: "test2",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test2@mail.com",
},
],
},
{
id: "external_id3",
displayName: "test3",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test3@mail.com",
},
],
},
{
id: "external_id4",
displayName: "test4",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test4@mail.com",
},
],
},
{
id: "external_id5",
displayName: "test5",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test5@mail.com",
},
],
},
{
id: "external_id6",
displayName: "test6",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test6@mail.com",
},
],
},
];
return AdB2cMockUsers;
}
}

View File

@ -0,0 +1,24 @@
import { InvocationContext } from "@azure/functions";
export class SendGridServiceMock {
/**
*
* @param to
* @param from
* @param subject
* @param text
* @param html
* @returns mail
*/
async sendMail(
context: InvocationContext,
to: string[],
cc: string[],
from: string,
subject: string,
text: string,
html: string
): Promise<void> {
return;
}
}

View File

@ -173,7 +173,7 @@ export const makeTestAccount = async (
};
};
export const createLicense = async (
export const createAndAllocateLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date | null,
@ -201,6 +201,29 @@ export const createLicense = async (
updated_at: new Date(),
});
identifiers.pop() as License;
// 割り当てるユーザーがいない場合は履歴を作成しない
if (!allocated_user_id) {
return;
}
// switch_from_typeを作成
// typeが"CARD"の場合は"CARD","TRIAL"の場合は"TRIAL","NORMAL"の場合は"NONE"を設定
const switch_from_type =
type === "CARD" ? "CARD" : type === "TRIAL" ? "TRIAL" : "NONE";
// ライセンスの割り当て履歴を作成
await datasource.getRepository(LicenseAllocationHistory).insert({
license_id: licenseId,
account_id: accountId,
user_id: allocated_user_id ?? -1,
is_allocated: true,
switch_from_type: switch_from_type,
executed_at: new Date(),
created_by: "test_runner",
created_at: new Date(),
updated_by: "updater",
updated_at: new Date(),
});
};
export const selectLicenseByAllocatedUser = async (

View File

@ -1,6 +1,6 @@
import { DataSource } from "typeorm";
import { licenseAlertProcessing } from "../functions/licenseAlert";
import { makeTestAccount, createLicense } from "./common/utility";
import { makeTestAccount, createAndAllocateLicense } from "./common/utility";
import * as dotenv from "dotenv";
import {
DateWithDayEndTime,
@ -8,13 +8,13 @@ import {
ExpirationThresholdDate,
NewTrialLicenseExpirationDate,
} from "../common/types/types";
import { AdB2cUser } from "../adb2c/types/types";
import { ADB2C_SIGN_IN_TYPE } from "../constants";
import { SendGridService } from "../sendgrid/sendgrid";
import { AdB2cService } from "../adb2c/adb2c";
import { InvocationContext } from "@azure/functions";
import { RedisClient, createClient } from "redis-mock";
import { createClient } from "redis-mock";
import { promisify } from "util";
import { SendGridServiceMock } from "./common/sendGrid.mock";
import { AdB2cServiceMock } from "./common/adb2c.mock";
describe("licenseAlert", () => {
dotenv.config({ path: ".env" });
@ -56,7 +56,7 @@ describe("licenseAlert", () => {
{ tier: 5 },
{ external_id: "external_id1", accepted_dpa_version: null }
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -103,7 +103,7 @@ describe("licenseAlert", () => {
accepted_dpa_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -147,7 +147,7 @@ describe("licenseAlert", () => {
{ tier: 5 },
{ external_id: "external_id3", accepted_dpa_version: null }
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -159,7 +159,7 @@ describe("licenseAlert", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
expiryDate,
@ -206,7 +206,7 @@ describe("licenseAlert", () => {
accepted_dpa_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -254,7 +254,7 @@ describe("licenseAlert", () => {
accepted_privacy_notice_version: null,
}
);
await createLicense(
await createAndAllocateLicense(
source,
1,
expiringSoonDate,
@ -278,97 +278,6 @@ describe("licenseAlert", () => {
});
});
// テスト用sendgrid
export class SendGridServiceMock {
/**
*
* @param to
* @param from
* @param subject
* @param text
* @param html
* @returns mail
*/
async sendMail(
to: string,
from: string,
subject: string,
text: string,
html: string
): Promise<void> {
return;
}
}
// テスト用adb2c
export class AdB2cServiceMock {
/**
* Azure AD B2Cからユーザ情報を取得する
* @param externalIds ID
* @returns
*/
async getUsers(
context: InvocationContext,
redisClient: RedisClient,
externalIds: string[]
): Promise<AdB2cUser[]> {
const AdB2cMockUsers: AdB2cUser[] = [
{
id: "external_id1",
displayName: "test1",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test1@mail.com",
},
],
},
{
id: "external_id2",
displayName: "test2",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test2@mail.com",
},
],
},
{
id: "external_id3",
displayName: "test3",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test3@mail.com",
},
],
},
{
id: "external_id4",
displayName: "test4",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test4@mail.com",
},
],
},
{
id: "external_id5",
displayName: "test5",
identities: [
{
signInType: ADB2C_SIGN_IN_TYPE.EMAILADDRESS,
issuer: "issuer",
issuerAssignedId: "test5@mail.com",
},
],
},
];
return AdB2cMockUsers;
}
}

View File

@ -8,18 +8,24 @@ import {
import { DateWithDayEndTime } from "../common/types/types";
import {
makeTestAccount,
createLicense,
createAndAllocateLicense,
makeTestUser,
selectLicenseByAllocatedUser,
selectLicenseAllocationHistory,
} from "./common/utility";
import * as dotenv from "dotenv";
import { InvocationContext } from "@azure/functions";
import { AdB2cService } from "../adb2c/adb2c";
import { SendGridService } from "../sendgrid/sendgrid";
import { SendGridServiceMock } from "./common/sendGrid.mock";
import { AdB2cServiceMock } from "./common/adb2c.mock";
import { createClient } from "redis-mock";
describe("licenseAutoAllocation", () => {
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
let source: DataSource | null = null;
const redisClient = createClient();
beforeEach(async () => {
source = new DataSource({
type: "sqlite",
@ -40,33 +46,39 @@ describe("licenseAutoAllocation", () => {
it("有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
@ -90,10 +102,11 @@ describe("licenseAutoAllocation", () => {
const user7 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id5",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -105,7 +118,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -117,7 +130,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -129,7 +142,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
@ -141,7 +154,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
@ -153,7 +166,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
6,
currentDateEndTime,
@ -165,7 +178,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
7,
currentDateEndTime,
@ -183,7 +196,7 @@ describe("licenseAutoAllocation", () => {
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
4,
nextDate,
@ -204,7 +217,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -222,7 +235,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + 30);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
200,
date,
@ -235,7 +248,13 @@ describe("licenseAutoAllocation", () => {
null
);
await licenseAutoAllocationProcessing(context, source);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -277,11 +296,349 @@ describe("licenseAutoAllocation", () => {
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type
).toBe("CARD");
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(4);
});
it("新規ライセンスがある場合でも、有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const testNoneUser1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const testAuthorUser2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const testTypistUser3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
const testNoneUser4ExpirationRemain = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 更新対象ではないユーザーauto_renewがfalse
const testNoneUser5AutoRenewFalse = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
auto_renew: false,
});
// 更新対象のユーザーAuthor二人目
const testAuthorUser6 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id5",
});
// 更新対象のユーザー(ただしライセンスが足りない)
const testNoneUser7lackofLicenses = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
});
// 割り当て済みで有効期限が本日のライセンス
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser1.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testAuthorUser2.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testTypistUser3.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser5AutoRenewFalse.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
6,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testAuthorUser6.id,
null,
null,
null
);
await createAndAllocateLicense(
source,
7,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser7lackofLicenses.id,
null,
null,
null
);
// 割り当て済みの更新対象ではないライセンス
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createAndAllocateLicense(
source,
4,
nextDate,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
testNoneUser4ExpirationRemain.id,
null,
null,
null
);
// 有効期限が先のライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 4; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createAndAllocateLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
// account1用の有効期限が設定されていないライセンスを作成
await createAndAllocateLicense(
source,
99,
null,
account1.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
// account2用の有効期限が設定されていないライセンスを作成
await createAndAllocateLicense(
source,
200,
null,
account2.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.REUSABLE,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const testNoneUser1Allocated = await selectLicenseByAllocatedUser(
source,
testNoneUser1.id
);
const testAuthorUser2Allocated = await selectLicenseByAllocatedUser(
source,
testAuthorUser2.id
);
const testTypistUser3Allocated = await selectLicenseByAllocatedUser(
source,
testTypistUser3.id
);
const testNoneUser4ExpirationRemainAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser4ExpirationRemain.id
);
const testNoneUser5AutoRenewFalseAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser5AutoRenewFalse.id
);
const testAuthorUser6Allocated = await selectLicenseByAllocatedUser(
source,
testAuthorUser6.id
);
const testNoneUser7lackofLicensesAllocated =
await selectLicenseByAllocatedUser(
source,
testNoneUser7lackofLicenses.id
);
const admin2Allocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
const testNoneUser1LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testNoneUser1.id, 99);
const testAuthorUser2LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testAuthorUser2.id, 101);
const testTypistUser3LicenseAllocationHistory =
await selectLicenseAllocationHistory(source, testTypistUser3.id, 103);
// Author、Typist、Noneの優先順位で割り当てられていることを確認
// 複数Authorがいる場合、それぞれに割り当てられていることを確認
expect(testAuthorUser2Allocated.license?.id).toBe(101);
expect(testAuthorUser6Allocated.license?.id).toBe(102);
expect(testTypistUser3Allocated.license?.id).toBe(103);
expect(testNoneUser1Allocated.license?.id).toBe(99);
// 有効期限がまだあるので、ライセンスが更新されていないことを確認
expect(testNoneUser4ExpirationRemainAllocated.license?.id).toBe(4);
// auto_renewがfalseなので、ライセンスが更新されていないことを確認
expect(testNoneUser5AutoRenewFalseAllocated.license?.id).toBe(5);
// ライセンスが足りない場合、ライセンスが更新されていないことを確認
expect(testNoneUser7lackofLicensesAllocated.license?.id).toBe(7);
// 複数アカウント分の処理が正常に行われていることの確認
expect(admin2Allocated.license?.id).toBe(200);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testNoneUser1.id);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory?.account_id
).toBe(account1.account.id);
expect(
testNoneUser1LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("CARD");
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testAuthorUser2.id);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.account_id
).toBe(account1.account.id);
expect(
testAuthorUser2LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("NONE");
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory?.user_id
).toBe(testTypistUser3.id);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.is_allocated
).toBe(true);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.account_id
).toBe(account1.account.id);
expect(
testTypistUser3LicenseAllocationHistory.licenseAllocationHistory
?.switch_from_type
).toBe("TRIAL");
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(5);
});
it("有効期限が指定日のライセンスが自動更新されること(リトライ用)", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
// 2023/11/22の日付を作成
const date1122 = new Date(2023, 10, 22, 23, 59, 59);
const currentDateEndTime = new DateWithDayEndTime(date1122);
@ -289,26 +646,29 @@ describe("licenseAutoAllocation", () => {
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id6" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 更新対象ではないユーザー(まだ有効期限が残っている)
@ -325,7 +685,7 @@ describe("licenseAutoAllocation", () => {
});
// 割り当て済みで有効期限が11/22のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -337,7 +697,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -349,7 +709,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -361,7 +721,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
20,
currentDateEndTime,
@ -373,7 +733,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
5,
currentDateEndTime,
@ -390,7 +750,7 @@ describe("licenseAutoAllocation", () => {
nextDate.setDate(date1122.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
4,
nextDate,
@ -409,7 +769,7 @@ describe("licenseAutoAllocation", () => {
// 2023/11/22の日付を作成
const date = new Date(2023, 10, 22, 23, 59, 59);
date.setDate(date.getDate() + i);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -427,7 +787,7 @@ describe("licenseAutoAllocation", () => {
dateMarch31.setMonth(12);
dateMarch31.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
dateMarch31.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
200,
dateMarch31,
@ -439,7 +799,14 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await licenseAutoAllocationProcessing(context, source, date1122);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock,
date1122
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -475,36 +842,50 @@ describe("licenseAutoAllocation", () => {
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
// メール送信が行われていることを確認
expect(spySend).toHaveBeenCalledTimes(4);
});
it("新たに割り当てられるライセンスが存在しないため、ライセンスが自動更新されない(エラーではない)", async () => {
it("新たに割り当てられるライセンスが存在しないアカウントは、ライセンスが自動更新されない(エラーではない)", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
// アカウントと管理者
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
const account2 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id5" }
);
// 更新対象のユーザー3role分
// account1の更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -516,7 +897,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -528,7 +909,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -540,20 +921,67 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createAndAllocateLicense(
source,
4,
currentDateEndTime,
account2.account.id,
LICENSE_TYPE.TRIAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
account2.admin.id,
null,
null,
null
);
await licenseAutoAllocationProcessing(context, source);
// account2の有効期限が先の未割当ライセンスを作成
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
nextDate.setMilliseconds(0);
await createAndAllocateLicense(
source,
100,
nextDate,
account2.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
const account2AdminAllocated = await selectLicenseByAllocatedUser(
source,
account2.admin.id
);
// ライセンスが更新されていないことを確認
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
expect(account2AdminAllocated.license?.id).toBe(100);
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(1);
});
it("tier4のアカウントのため、ライセンスが自動更新されない", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
@ -561,25 +989,28 @@ describe("licenseAutoAllocation", () => {
const account1 = await makeTestAccount(
source,
{ tier: 4 },
{ role: `${USER_ROLES.NONE}` }
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id2",
});
const user2 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.AUTHOR}`,
external_id: "external_id3",
});
const user3 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.TYPIST}`,
external_id: "external_id4",
});
// 割り当て済みで有効期限が本日のライセンス
await createLicense(
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
@ -591,7 +1022,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
2,
currentDateEndTime,
@ -603,7 +1034,7 @@ describe("licenseAutoAllocation", () => {
null,
null
);
await createLicense(
await createAndAllocateLicense(
source,
3,
currentDateEndTime,
@ -624,7 +1055,7 @@ describe("licenseAutoAllocation", () => {
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createLicense(
await createAndAllocateLicense(
source,
i + 100,
date,
@ -638,7 +1069,13 @@ describe("licenseAutoAllocation", () => {
);
}
await licenseAutoAllocationProcessing(context, source);
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const user2Allocated = await selectLicenseByAllocatedUser(source, user2.id);
const user3Allocated = await selectLicenseByAllocatedUser(source, user3.id);
@ -646,5 +1083,99 @@ describe("licenseAutoAllocation", () => {
expect(user1Allocated.license?.id).toBe(1);
expect(user2Allocated.license?.id).toBe(2);
expect(user3Allocated.license?.id).toBe(3);
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(0);
});
it("メール送信に失敗しても、有効期限が本日のライセンスが自動更新されること", async () => {
if (!source) fail();
const context = new InvocationContext();
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDateEndTime = new DateWithDayEndTime();
// アカウント
const account1 = await makeTestAccount(
source,
{ tier: 5 },
{ role: `${USER_ROLES.NONE}`, external_id: "external_id1" }
);
// 更新対象のユーザー3role分
const user1 = await makeTestUser(source, {
account_id: account1.account.id,
role: `${USER_ROLES.NONE}`,
external_id: "external_id7", // メール送信失敗用
});
// 割り当て済みで有効期限が本日のライセンス
await createAndAllocateLicense(
source,
1,
currentDateEndTime,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
user1.id,
null,
null,
null
);
// 有効期限が先の未割当ライセンスを作成
// idが100のものは有効期限が当日なので自動割り当て対象外
// idが101のものから割り当てられる
for (let i = 0; i < 5; i++) {
const date = new Date();
date.setDate(date.getDate() + i);
date.setHours(23, 59, 59); // 時分秒を"23:59:59"に固定
date.setMilliseconds(0);
await createAndAllocateLicense(
source,
i + 100,
date,
account1.account.id,
LICENSE_TYPE.CARD,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
null,
null,
null,
null
);
}
await licenseAutoAllocationProcessing(
context,
source,
redisClient,
sendgridMock,
adb2cMock
);
const user1Allocated = await selectLicenseByAllocatedUser(source, user1.id);
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
user1.id,
101
);
// 割り当てられていることを確認
expect(user1Allocated.license?.id).toBe(101);
// ライセンス割り当て履歴テーブルが更新されていることを確認
expect(licenseAllocationHistory.licenseAllocationHistory?.user_id).toBe(
user1.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.is_allocated
).toBe(true);
expect(licenseAllocationHistory.licenseAllocationHistory?.account_id).toBe(
account1.account.id
);
expect(
licenseAllocationHistory.licenseAllocationHistory?.switch_from_type
).toBe("CARD");
// メール送信が行われていないことを確認
expect(spySend).toHaveBeenCalledTimes(0);
});
});

View File

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"target": "ES2021",
"outDir": "dist",
"rootDir": ".",
"sourceMap": true,