From 85fdec2e5aef64c88ce43da40ffb22999705a857 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Wed, 25 Sep 2024 01:06:33 +0000 Subject: [PATCH] Merged PR 922: Functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4485: Functions](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4485) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 何をどう変更したか、追加したライブラリなど - このPull Requestでの対象/対象外 - 影響範囲(他の機能にも影響があるか) ## レビューポイント - 特にレビューしてほしい箇所 - 軽微なものや自明なものは記載不要 - 修正範囲が大きい場合などに記載 - 全体的にや仕様を満たしているか等は本当に必要な時のみ記載 - 修正箇所がほかの機能に影響していないか ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## クエリの変更 - Repositoryを変更し、クエリが変更された場合は変更内容を確認する - Before/Afterのクエリ - クエリ置き場 ## 動作確認状況 - ローカルで確認、develop環境で確認など - 行った修正がデグレを発生させていないことを確認できるか - 具体的にどのような確認をしたか - どのケースに対してどのような手段でデグレがないことを担保しているか ## 補足 - 相談、参考資料などがあれば --- .../src/functions/licenseAutoAllocation.ts | 7 +- .../src/sendgrid/sendgrid.spec.ts | 133 ++++++++++++++++++ dictation_function/src/sendgrid/sendgrid.ts | 16 ++- .../src/gateways/sendgrid/sendgrid.service.ts | 12 +- .../src/gateways/sendgrid/sendgrid.spec.ts | 133 ++++++++++++++++++ 5 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 dictation_function/src/sendgrid/sendgrid.spec.ts create mode 100644 dictation_server/src/gateways/sendgrid/sendgrid.spec.ts diff --git a/dictation_function/src/functions/licenseAutoAllocation.ts b/dictation_function/src/functions/licenseAutoAllocation.ts index 8b1b053..c37df17 100644 --- a/dictation_function/src/functions/licenseAutoAllocation.ts +++ b/dictation_function/src/functions/licenseAutoAllocation.ts @@ -667,13 +667,14 @@ export async function sendMailWithU108( .replaceAll(USER_EMAIL, userMail) .replaceAll(TOP_URL, url); } - const ccAddress = customerAdminMails.includes(userMail) ? [] : [userMail]; + const uniqueCustomerAdminMails = [...new Set(customerAdminMails)]; + const ccMails = uniqueCustomerAdminMails.includes(userMail) ? [] : [userMail]; // メールを送信する await sendGrid.sendMail( context, - customerAdminMails, - ccAddress, + uniqueCustomerAdminMails, + ccMails, mailFrom, subject, text, diff --git a/dictation_function/src/sendgrid/sendgrid.spec.ts b/dictation_function/src/sendgrid/sendgrid.spec.ts new file mode 100644 index 0000000..8d42fea --- /dev/null +++ b/dictation_function/src/sendgrid/sendgrid.spec.ts @@ -0,0 +1,133 @@ +import { SendGridService } from "./sendgrid"; +import sendgrid from "@sendgrid/mail"; +import { InvocationContext } from "@azure/functions"; + +// sendgridのsend関数をモック化 +jest.mock("@sendgrid/mail", () => ({ + send: jest.fn(), + setApiKey: jest.fn(), +})); + +describe("SendGridService", () => { + let service: SendGridService; + let mockContext: Partial; + + beforeEach(() => { + process.env.SENDGRID_API_KEY = "dummy_key"; // 必要な環境変数 + service = new SendGridService(); // SendGridServiceのインスタンスを作成 + mockContext = { + log: jest.fn(), + warn: jest.fn(), + }; // InvocationContextのモック + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("should send an email with no duplicate to and cc addresses", async () => { + // モックデータ + const to = ["test1@example.com", "test2@example.com"]; + const cc = ["test3@example.com"]; + const from = "sender@example.com"; + const subject = "Test Subject"; + const text = "Test Text"; + const html = "

Test HTML

"; + + // sendgrid.sendのモック + (sendgrid.send as jest.Mock).mockResolvedValue([ + { statusCode: 202, body: "OK" }, + ]); + + // メール送信を実行 + await service.sendMail( + mockContext as InvocationContext, + to, + cc, + from, + subject, + text, + html + ); + + // sendgrid.sendが呼ばれたことを確認 + expect(sendgrid.send).toHaveBeenCalledWith({ + from: { email: from }, + to: to.map((v) => ({ email: v })), + cc: cc.map((v) => ({ email: v })), + subject, + text, + html, + }); + + // ログが出力されているか確認 + expect(mockContext.log).toHaveBeenCalledWith(`[IN] sendMail`); + expect(mockContext.log).toHaveBeenCalledWith(`[OUT] sendMail`); + }); + + it("should remove duplicate addresses in to and cc", async () => { + const to = ["test1@example.com", "test2@example.com", "test1@example.com"]; // 重複あり + const cc = ["test2@example.com", "test3@example.com"]; // 重複あり + const from = "sender@example.com"; + const subject = "Test Subject"; + const text = "Test Text"; + const html = "

Test HTML

"; + + // sendgrid.sendのモック + (sendgrid.send as jest.Mock).mockResolvedValue([ + { statusCode: 202, body: "OK" }, + ]); + + // メール送信を実行 + await service.sendMail( + mockContext as InvocationContext, + to, + cc, + from, + subject, + text, + html + ); + + // 重複が削除されているか確認 + expect(sendgrid.send).toHaveBeenCalledWith({ + from: { email: from }, + to: [{ email: "test1@example.com" }, { email: "test2@example.com" }], // 重複削除後 + cc: [{ email: "test3@example.com" }], // ccから重複が削除される + subject, + text, + html, + }); + }); + + it("should log an error when send fails", async () => { + const to = ["test1@example.com"]; + const cc = ["test2@example.com"]; + const from = "sender@example.com"; + const subject = "Test Subject"; + const text = "Test Text"; + const html = "

Test HTML

"; + + // sendgrid.sendがエラーを投げるモック + (sendgrid.send as jest.Mock).mockRejectedValue(new Error("Send failed")); + + // エラーが投げられるか確認 + await expect( + service.sendMail( + mockContext as InvocationContext, + to, + cc, + from, + subject, + text, + html + ) + ).rejects.toThrow("Send failed"); + + // エラーログが出力されているか確認 + expect(mockContext.warn).toHaveBeenCalledWith("send mail faild."); + expect(mockContext.warn).toHaveBeenCalledWith( + expect.stringContaining("sendMail error=Error: Send failed") + ); + }); +}); diff --git a/dictation_function/src/sendgrid/sendgrid.ts b/dictation_function/src/sendgrid/sendgrid.ts index ef8e049..8836f45 100644 --- a/dictation_function/src/sendgrid/sendgrid.ts +++ b/dictation_function/src/sendgrid/sendgrid.ts @@ -32,22 +32,28 @@ export class SendGridService { ): Promise { context.log(`[IN] ${this.sendMail.name}`); try { + // 1. toの重複を削除 + const uniqueTo = [...new Set(to)]; + + // 2. ccの重複を削除 + let uniqueCc = [...new Set(cc)]; + + // 3. toとccの重複を削除(cc側から削除) + uniqueCc = uniqueCc.filter((email) => !uniqueTo.includes(email)); const res = await sendgrid .send({ from: { email: from, }, - to: to.map((v) => ({ email: v })), - cc: cc.map((v) => ({ email: v })), + to: uniqueTo.map((v) => ({ email: v })), + cc: uniqueCc.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)}`, + ` status code: ${res.statusCode} body: ${JSON.stringify(res.body)}` ); } catch (e) { context.warn(`send mail faild.`); diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts index 8d90c20..46226f8 100644 --- a/dictation_server/src/gateways/sendgrid/sendgrid.service.ts +++ b/dictation_server/src/gateways/sendgrid/sendgrid.service.ts @@ -1622,13 +1622,21 @@ export class SendGridService { ): Promise { this.logger.log(`[IN] [${context.getTrackingId()}] ${this.sendMail.name}`); try { + // toの重複を削除 + const uniqueTo = [...new Set(to)]; + + // ccの重複を削除 + let uniqueCc = [...new Set(cc)]; + + // toとccの重複を削除(cc側から削除) + uniqueCc = uniqueCc.filter((email) => !uniqueTo.includes(email)); const res = await sendgrid .send({ from: { email: from, }, - to: to.map((v) => ({ email: v })), - cc: cc.map((v) => ({ email: v })), + to: uniqueTo.map((v) => ({ email: v })), + cc: uniqueCc.map((v) => ({ email: v })), subject: subject, text: text, html: html, diff --git a/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts b/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts new file mode 100644 index 0000000..d93dd8b --- /dev/null +++ b/dictation_server/src/gateways/sendgrid/sendgrid.spec.ts @@ -0,0 +1,133 @@ +import sendgrid from '@sendgrid/mail'; +import { SendGridService } from './sendgrid.service'; +import { Context, makeContext } from '../../common/log'; + +// sendgridのsend関数をモック化 +jest.mock('@sendgrid/mail', () => ({ + send: jest.fn(), + setApiKey: jest.fn(), +})); + +describe('SendGridService', () => { + let context: Context; + let configServiceMock: any; + let loggerMock: any; + + beforeEach(() => { + // Loggerのモックを作成 + loggerMock = { + log: jest.fn(), + error: jest.fn(), + }; + configServiceMock = { + getOrThrow: jest.fn(() => 'dummy'), + get: jest.fn(() => 'SENDGRID_API_KEY'), + }; + + // sendgridのAPIキーを設定 + sendgrid.setApiKey(configServiceMock.get('SENDGRID_API_KEY')); + + // クラスにモックのloggerを注入 + (SendGridService as any).logger = loggerMock; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('宛先とccアドレスが重複しないメールを送信すること', async () => { + // モックデータ + const to = ['test1@example.com', 'test2@example.com']; + const cc = ['test3@example.com']; + const from = 'sender@example.com'; + const subject = 'Test Subject'; + const text = 'Test Text'; + const html = '

Test HTML

'; + + // sendgrid.sendのモック + (sendgrid.send as jest.Mock).mockResolvedValue([ + { statusCode: 202, body: 'OK' }, + ]); + + // メール送信を実行 + await new SendGridService(configServiceMock).sendMail( + makeContext('trackingId', 'requestId'), + to, + cc, + from, + subject, + text, + html, + ); + + // sendgrid.sendが呼ばれたことを確認 + expect(sendgrid.send).toHaveBeenCalledWith({ + from: { email: from }, + to: to.map((v) => ({ email: v })), + cc: cc.map((v) => ({ email: v })), + subject, + text, + html, + }); + }); + + it('宛先と CC に重複したメールアドレスがあった場合、削除してからメールを送信すること(宛先とCCの重複があったときはCCを削除すること)', async () => { + const to = ['test1@example.com', 'test2@example.com', 'test1@example.com']; // 重複あり + const cc = ['test2@example.com', 'test3@example.com']; // 重複あり + const from = 'sender@example.com'; + const subject = 'Test Subject'; + const text = 'Test Text'; + const html = '

Test HTML

'; + + // sendgrid.sendのモック + (sendgrid.send as jest.Mock).mockResolvedValue([ + { statusCode: 202, body: 'OK' }, + ]); + + // メール送信を実行 + await new SendGridService(configServiceMock).sendMail( + makeContext('trackingId', 'requestId'), + to, + cc, + from, + subject, + text, + html, + ); + + // 重複が削除されているか確認 + expect(sendgrid.send).toHaveBeenCalledWith({ + from: { email: from }, + to: [{ email: 'test1@example.com' }, { email: 'test2@example.com' }], // 重複削除後 + cc: [{ email: 'test3@example.com' }], // ccから重複が削除される + subject, + text, + html, + }); + }); + + it('送信に失敗したときはエラーを記録すること', async () => { + const to = ['test1@example.com']; + const cc = ['test2@example.com']; + const from = 'sender@example.com'; + const subject = 'Test Subject'; + const text = 'Test Text'; + const html = '

Test HTML

'; + + // sendgrid.sendがエラーを投げるモック + (sendgrid.send as jest.Mock).mockRejectedValue(new Error('Send failed')); + + // エラーが投げられるか確認 + await expect( + new SendGridService(configServiceMock).sendMail( + makeContext('trackingId', 'requestId'), + to, + cc, + from, + subject, + text, + html, + ), + ).rejects.toThrow('Send failed'); + }); +});