Merge branch 'main' into develop

This commit is contained in:
makabe 2024-05-07 12:04:03 +09:00
commit 23f8b54011
20 changed files with 1523 additions and 185 deletions

View File

@ -408,3 +408,13 @@ export const LICENSE_COUNT_ANALYSIS_FRONT_STRING = "LicenseAggregated";
* @const {string}
*/
export const LICENSE_COUNT_ANALYSIS_CONTAINER_NAME = "analysis-licenses";
/**
*
* @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

@ -387,7 +387,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -447,7 +449,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.secondaryAdminEmail,
context,
[targetAccount.secondaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -506,7 +510,9 @@ async function sendAlertMail(
// メールを送信
try {
await sendgrid.sendMail(
targetAccount.primaryAdminEmail,
context,
[targetAccount.primaryAdminEmail],
[],
mailFrom,
subject,
text,
@ -564,7 +570,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 {
@ -17,10 +23,19 @@ import {
NewAllocatedLicenseExpirationDate,
} from "../common/types/types";
import { initializeDataSource } from "../database/initializeDataSource";
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 {
@ -33,6 +48,7 @@ export async function licenseAutoAllocationProcessing(
currentDateZeroTime = new DateWithZeroTime(dateToTrigger);
currentDateEndTime = new DateWithDayEndTime(dateToTrigger);
}
// 自動更新対象の候補となるアカウントを取得
const accountRepository = datasource.getRepository(Account);
const targetAccounts = await accountRepository.find({
@ -56,6 +72,9 @@ export async function licenseAutoAllocationProcessing(
await allocateLicense(
context,
datasource,
redisClient,
sendGrid,
adb2c,
autoAllocationList,
currentDateZeroTime,
currentDateEndTime
@ -78,8 +97,42 @@ export async function licenseAutoAllocation(
context.log("[IN]licenseAutoAllocation");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = await initializeDataSource(context);
await licenseAutoAllocationProcessing(context, datasource);
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, LicenseAllocationHistory],
});
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;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c
);
} catch (e) {
context.log("licenseAutoAllocation failed.");
context.error(e);
@ -199,24 +252,45 @@ export async function getAutoAllocatableLicense(
try {
context.log("[IN]getAutoAllocatableLicense");
// 割り当て可能なライセンスを取得
const license = await licenseRepository.findOne({
where: {
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime) || null,
},
order: {
expiry_date: "ASC",
},
const license = await licenseRepository.find({
where: [
{
account_id: accountId,
status: In([
LICENSE_ALLOCATED_STATUS.REUSABLE,
LICENSE_ALLOCATED_STATUS.UNALLOCATED,
]),
expiry_date: MoreThan(currentDateEndTime),
},
{
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.");
@ -237,17 +311,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> {
context.log("[IN]allocateLicense");
try {
context.log("[IN]allocateLicense");
// 自動更新対象ユーザーにライセンスを割り当てる
// 割り当て可能なライセンスが存在するかどうかのフラグ
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
@ -276,6 +356,7 @@ export async function allocateLicense(
});
if (!allocatedLicense) {
context.log(`skip auto allocation. userID:${userId}`);
hasAutoRenewLicense = false;
return;
}
@ -349,17 +430,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

@ -9,6 +9,10 @@ import { licenseAutoAllocationProcessing } from "./licenseAutoAllocation";
import * as dotenv from "dotenv";
import { HTTP_METHODS, HTTP_STATUS_CODES } from "../constants";
import { initializeDataSource } from "../database/initializeDataSource";
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,
@ -37,15 +41,52 @@ export async function licenseAutoAllocationManualRetry(
context.log("[IN]licenseAutoAllocationManualRetry");
dotenv.config({ path: ".env" });
dotenv.config({ path: ".env.local", override: true });
const datasource = await initializeDataSource(context);
await licenseAutoAllocationProcessing(context, datasource, dateToTrigger);
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, LicenseAllocationHistory],
});
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;
}
const sendGrid = new SendGridService();
const adb2c = new AdB2cService();
await licenseAutoAllocationProcessing(
context,
datasource,
redisClient,
sendGrid,
adb2c,
dateToTrigger
);
context.log("Automatic license allocation has been triggered.");
return {
status: HTTP_STATUS_CODES.OK,
body: "Automatic license allocation has been triggered.",
};
} else {
context.log(`Please use the POST method. Requested method = [${req.method}]`);
context.log(
`Please use the POST method. Requested method = [${req.method}]`
);
return {
status: HTTP_STATUS_CODES.BAD_REQUEST,
body: `Please use the POST method. method = [${req.method}]`,

View File

@ -1,3 +1,4 @@
import { InvocationContext } from "@azure/functions";
import sendgrid from "@sendgrid/mail";
import { error } from "console";
@ -9,9 +10,11 @@ export class SendGridService {
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
}
/**
/**
*
* @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

@ -233,6 +233,59 @@ export const createLicense = async (
});
identifiers.pop() as License;
};
export const createAndAllocateLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date | null,
accountId: number,
type: string,
status: string,
allocated_user_id: number | null,
order_id: number | null,
deleted_at: Date | null,
delete_order_id: number | null,
created_at?: Date
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
expiry_date: expiry_date,
account_id: accountId,
type: type,
status: status,
allocated_user_id: allocated_user_id,
order_id: order_id,
deleted_at: deleted_at,
delete_order_id: delete_order_id,
created_by: "test_runner",
created_at: created_at ? created_at : new Date(),
updated_by: "updater",
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 createLicenseAllocationHistory = async (
datasource: DataSource,

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.test", 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,

View File

@ -36,4 +36,5 @@ EMAIL_CONFIRM_LIFETIME=86400
REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass
ADB2C_CACHE_TTL=86400
ADB2C_CACHE_TTL=86400
DEALER_ACCOUNT_ID_HIDDEN_LIST=1,2,3,4

View File

@ -37,4 +37,5 @@ REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass
ADB2C_CACHE_TTL=86400
TEMPLATE_ROOT=dist
TEMPLATE_ROOT=dist
DEALER_ACCOUNT_ID_HIDDEN_LIST=50,99

View File

@ -36,6 +36,10 @@ export class EnvValidator {
@IsString()
DB_PASSWORD: string;
@IsOptional()
@IsString()
DEALER_ACCOUNT_ID_HIDDEN_LIST: string;
// .env.local
@IsOptional()
@IsString()

View File

@ -2818,6 +2818,35 @@ describe('getDealers', () => {
],
});
});
it('非表示指定されたDealer以外のDealerを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 100件のDealerを作成し、country,id,company_nameを取得する
const dealers: { country: string; id: number; name: string }[] = [];
for (let i = 0; i < 100; i++) {
const { id, company_name, country } = (
await makeTestAccount(source, {
parent_account_id: i,
tier: TIERS.TIER4,
country: 'JP',
company_name: `DEALER_${i}`,
})
).account;
dealers.push({ id, name: company_name, country });
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(`uuidv4`, 'requestId');
const result = await service.getDealers(context);
// idが50と99のDealerを非表示にする
expect(result.dealers.length).toBe(98);
expect(result).toEqual({
dealers: dealers.filter((dealer) => dealer.id !== 50 && dealer.id !== 99),
});
});
it('0件でもDealerを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -7454,6 +7483,7 @@ describe('deleteAccountAndData', () => {
expect(expectUserIds).toStrictEqual(userArchiveIds);
});
});
describe('getAccountInfoMinimalAccess', () => {
let source: DataSource | null = null;
beforeAll(async () => {
@ -9890,7 +9920,9 @@ describe('updatePartnerInfo', () => {
subject: string,
text: string,
html: string,
) => {},
) => {
// empty
},
});
overrideAdB2cService(service, {
getUser: async (context, externalId) => {
@ -9977,7 +10009,9 @@ describe('updatePartnerInfo', () => {
subject: string,
text: string,
html: string,
) => {},
) => {
// empty
},
});
overrideAdB2cService(service, {
getUser: async (context, externalId) => {

View File

@ -82,9 +82,13 @@ import {
WorktypeIdNotFoundError,
} from '../../repositories/worktypes/errors/types';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AccountsService {
//プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応
private readonly dealerAccountIdHiddenList: number[] = [];
private readonly logger = new Logger(AccountsService.name);
constructor(
private readonly accountRepository: AccountsRepositoryService,
private readonly licensesRepository: LicensesRepositoryService,
@ -94,8 +98,27 @@ export class AccountsService {
private readonly adB2cService: AdB2cService,
private readonly sendgridService: SendGridService,
private readonly blobStorageService: BlobstorageService,
) {}
private readonly logger = new Logger(AccountsService.name);
private readonly configService: ConfigService,
) {
const dealerAccountIdList = this.configService.get<string>(
'DEALER_ACCOUNT_ID_HIDDEN_LIST',
);
// ディーラーアカウントIDリストを数値配列に変換する
// 変換できない場合はエラーをスローする
// 存在しない場合や空文字列の場合は空の配列を返す
if (dealerAccountIdList) {
this.dealerAccountIdHiddenList = dealerAccountIdList
.split(',')
.map((x) => {
const id = parseInt(x, 10);
if (isNaN(id)) {
throw new Error('DEALER_ACCOUNT_ID_HIDDEN_LIST is invalid');
}
return id;
});
}
}
/**
*
* @param accountId
@ -1192,9 +1215,26 @@ export class AccountsService {
const dealerAccounts = await this.accountRepository.findDealerAccounts(
context,
);
// プロダクト バックログ項目 4077: [保守]本番環境動作確認用のDealerアカウントを表示しないようにする の対応
// this.dealerAccountIdHiddenListに含まれるアカウント(動作確認用のアカウント)を除外する。
// 除外したアカウントをlogに出力する
const filteredDealerAccounts = dealerAccounts.filter((dealerAccount) => {
const isHidden = this.dealerAccountIdHiddenList.includes(
dealerAccount.id,
);
if (isHidden) {
this.logger.log(
`[${context.getTrackingId()}] hidden dealer account: ${
dealerAccount.id
}`,
);
}
return !isHidden;
});
// レスポンス用の型に変換
const dealers: GetDealersResponse = {
dealers: dealerAccounts.map((dealerAccount): Dealer => {
dealers: filteredDealerAccounts.map((dealerAccount): Dealer => {
return {
id: dealerAccount.id,
name: dealerAccount.company_name,