From 474cdd56c6133187992b24089dfa00e1cbfdc087 Mon Sep 17 00:00:00 2001 From: "saito.k" Date: Mon, 10 Jun 2024 02:31:35 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=20913:=20=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B9=E5=8F=96=E5=BE=97API=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task4220: ライセンス取得APIの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4220) - 割り当て可能ライセンス取得APIを修正 - 有効期限が当日のライセンスは割り当て可能としないように修正 - 有効期限が翌日以降のライセンスを取得 - テスト修正 - ライセンス割り当ての処理にコメント追加 - 結果として有効期限が当日のライセンスは割り当てられないので問題はないが、記述方法が紛らわしいのでコメントを追加した。 ## レビューポイント - テストケースは足りているか - 追加したコメントの意図は伝わるか ## クエリの変更 - Repositoryを変更し、クエリが変更された場合は変更内容を確認する - クエリ部分を修正したが、基準となる日付を変更したのみなので、クエリ自体に変更はなし - Before/Afterで確認したが、変更はなかった ## 動作確認状況 - ローカルで確認 - 行った修正がデグレを発生させていないことを確認できるか - テストを追加して、デグレがないことを検証した ## 補足 - 相談、参考資料などがあれば --- .../licenses/licenses.service.spec.ts | 415 +++++++++++++++++- .../licenses/licenses.repository.service.ts | 14 +- 2 files changed, 418 insertions(+), 11 deletions(-) diff --git a/dictation_server/src/features/licenses/licenses.service.spec.ts b/dictation_server/src/features/licenses/licenses.service.spec.ts index 72ac6ea..37912c3 100644 --- a/dictation_server/src/features/licenses/licenses.service.spec.ts +++ b/dictation_server/src/features/licenses/licenses.service.spec.ts @@ -25,6 +25,7 @@ import { } from '../../constants'; import { makeHierarchicalAccounts, + makeTestAccount, makeTestSimpleAccount, makeTestUser, } from '../../common/test/utility'; @@ -387,8 +388,12 @@ describe('カードライセンスを取り込む', () => { if (!source) fail(); const module = await makeTestingModule(source); if (!module) fail(); + // 明日の日付を取得 + // ミリ秒以下は切り捨てる + const tommorow = new Date(); + tommorow.setDate(tommorow.getDate() + 1); + tommorow.setMilliseconds(0); - const now = new Date(); const { id: accountId } = await makeTestSimpleAccount(source); const { external_id: externalId } = await makeTestUser(source, { account_id: accountId, @@ -402,7 +407,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 1, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -428,7 +433,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 3, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -441,7 +446,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 4, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -467,7 +472,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 6, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.ALLOCATED, @@ -480,7 +485,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 7, - new Date(now.getTime() + 60 * 60 * 1000 * 2), + new Date(tommorow.getTime() + 60 * 60 * 1000 * 2), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.DELETED, @@ -493,7 +498,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 8, - new Date(now.getTime() + 60 * 60 * 1000), + new Date(tommorow.getTime() + 60 * 60 * 1000), accountId + 1, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -506,7 +511,7 @@ describe('カードライセンスを取り込む', () => { await createLicense( source, 9, - new Date(now.getTime() - 60 * 60 * 1000 * 24), + new Date(tommorow.getTime() - 60 * 60 * 1000 * 24), accountId, LICENSE_TYPE.NORMAL, LICENSE_ALLOCATED_STATUS.UNALLOCATED, @@ -1610,3 +1615,397 @@ describe('ライセンス注文キャンセル', () => { ); }); }); + +describe('割り当て可能なライセンス取得', () => { + let source: DataSource | null = null; + beforeAll(async () => { + if (source == null) { + source = await (async () => { + const s = new DataSource({ + type: 'mysql', + host: 'test_mysql_db', + port: 3306, + username: 'user', + password: 'password', + database: 'odms', + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: false, // trueにすると自動的にmigrationが行われるため注意 + logger: new TestLogger('none'), + logging: true, + }); + return await s.initialize(); + })(); + } + }); + + beforeEach(async () => { + if (source) { + await truncateAllTable(source); + } + }); + + afterAll(async () => { + await source?.destroy(); + source = null; + }); + + it('割り当て可能なライセンスを取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が当日のライセンスを1つ、有効期限が翌日のライセンスを1つ、有効期限が翌々日のライセンスを1つ作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const today = new Date(); + today.setMilliseconds(0); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + tomorrow.setMilliseconds(0); + const dayAfterTomorrow = new Date(); + dayAfterTomorrow.setDate(dayAfterTomorrow.getDate() + 2); + dayAfterTomorrow.setMilliseconds(0); + await createLicense( + source, + 1, + today, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + await createLicense( + source, + 2, + tomorrow, + account.id, + LICENSE_TYPE.CARD, + LICENSE_ALLOCATED_STATUS.REUSABLE, + null, + null, + null, + null, + ); + await createLicense( + source, + 3, + dayAfterTomorrow, + account.id, + LICENSE_TYPE.TRIAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const license1 = await selectLicense(source, 1); + expect(license1.license?.id).toBe(1); + expect(license1.license?.expiry_date).toEqual(today); + expect(license1.license?.allocated_user_id).toBe(null); + expect(license1.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license1.license?.account_id).toBe(account.id); + expect(license1.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license2 = await selectLicense(source, 2); + expect(license2.license?.id).toBe(2); + expect(license2.license?.expiry_date).toEqual(tomorrow); + expect(license2.license?.allocated_user_id).toBe(null); + expect(license2.license?.status).toBe(LICENSE_ALLOCATED_STATUS.REUSABLE); + expect(license2.license?.account_id).toBe(account.id); + expect(license2.license?.type).toBe(LICENSE_TYPE.CARD); + const license3 = await selectLicense(source, 3); + expect(license3.license?.id).toBe(3); + expect(license3.license?.expiry_date).toEqual(dayAfterTomorrow); + expect(license3.license?.allocated_user_id).toBe(null); + expect(license3.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license3.license?.account_id).toBe(account.id); + expect(license3.license?.type).toBe(LICENSE_TYPE.TRIAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + + // 有効期限が当日のライセンスは取得されない + // 有効期限が長い順に取得される + expect(response.allocatableLicenses.length).toBe(2); + expect(response.allocatableLicenses[0].licenseId).toBe(3); + expect(response.allocatableLicenses[0].expiryDate).toEqual( + dayAfterTomorrow, + ); + expect(response.allocatableLicenses[1].licenseId).toBe(2); + expect(response.allocatableLicenses[1].expiryDate).toEqual(tomorrow); + }); + + it('割り当て可能なライセンスが存在しない場合、空の配列を返却する', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が当日のライセンスを3つ、有効期限が昨日のライセンスを1つ作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const today = new Date(); + today.setMilliseconds(0); + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + yesterday.setMilliseconds(0); + for (let i = 1; i <= 3; i++) { + await createLicense( + source, + i, + today, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + } + await createLicense( + source, + 4, + yesterday, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const license1 = await selectLicense(source, 1); + expect(license1.license?.id).toBe(1); + expect(license1.license?.expiry_date).toEqual(today); + expect(license1.license?.allocated_user_id).toBe(null); + expect(license1.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license1.license?.account_id).toBe(account.id); + expect(license1.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license2 = await selectLicense(source, 2); + expect(license2.license?.id).toBe(2); + expect(license2.license?.expiry_date).toEqual(today); + expect(license2.license?.allocated_user_id).toBe(null); + expect(license2.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license2.license?.account_id).toBe(account.id); + expect(license2.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license3 = await selectLicense(source, 3); + expect(license3.license?.id).toBe(3); + expect(license3.license?.expiry_date).toEqual(today); + expect(license3.license?.allocated_user_id).toBe(null); + expect(license3.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license3.license?.account_id).toBe(account.id); + expect(license3.license?.type).toBe(LICENSE_TYPE.NORMAL); + const license4 = await selectLicense(source, 4); + expect(license4.license?.id).toBe(4); + expect(license4.license?.expiry_date).toEqual(yesterday); + expect(license4.license?.allocated_user_id).toBe(null); + expect(license4.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license4.license?.account_id).toBe(account.id); + expect(license4.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 有効期限が当日のライセンスは取得されない + // 有効期限が切れているライセンスは取得されない + expect(response.allocatableLicenses.length).toBe(0); + expect(response.allocatableLicenses).toEqual([]); + }); + + it('割り当て可能なライセンスを100件取得できる', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // 有効期限が30日後のライセンスを100件作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const date = new Date(); + date.setDate(date.getDate() + 30); + date.setMilliseconds(0); + for (let i = 1; i <= 100; i++) { + await createLicense( + source, + i, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + } + // ライセンス作成したデータを確認 + for (let i = 1; i <= 100; i++) { + const license = await selectLicense(source, i); + expect(license.license?.id).toBe(i); + expect(license.license?.expiry_date).toEqual(date); + expect(license.license?.allocated_user_id).toBe(null); + expect(license.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license.license?.account_id).toBe(account.id); + expect(license.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 100件取得できる + expect(response.allocatableLicenses.length).toBe(100); + }); + it('既に割り当てられているライセンスは取得されない', async () => { + if (!source) fail(); + const module = await makeTestingModule(source); + if (!module) fail(); + const { account, admin } = await makeTestAccount(source, { + company_name: 'AAA', + tier: 5, + }); + // ライセンスを作成 + // ライセンスを5件作成(10日後から1日ずつ有効期限を設定) + // 有効期限が設定されていないライセンス(新規ライセンス)を1件作成 + // 既に割り当てられているライセンスを1件作成 + // ミリ秒以下は切り捨てる DBで扱っていないため + const date = new Date(); + date.setMinutes(0); + date.setSeconds(0); + date.setDate(date.getDate() + 10); + date.setMilliseconds(0); + for (let i = 1; i <= 5; i++) { + await createLicense( + source, + i, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + admin.id, + null, + null, + null, + ); + date.setDate(date.getDate() + 1); + } + // 新規ライセンス + await createLicense( + source, + 6, + null, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + null, + null, + null, + null, + ); + // 既に割り当てられているライセンス + await createLicense( + source, + 7, + date, + account.id, + LICENSE_TYPE.NORMAL, + LICENSE_ALLOCATED_STATUS.ALLOCATED, + admin.id, + null, + null, + null, + ); + // ライセンス作成したデータを確認 + { + const date = new Date(); + date.setMinutes(0); + date.setSeconds(0); + date.setDate(date.getDate() + 10); + date.setMilliseconds(0); + for (let i = 1; i <= 5; i++) { + const license = await selectLicense(source, i); + expect(license.license?.id).toBe(i); + expect(license.license?.expiry_date).toEqual(date); + expect(license.license?.allocated_user_id).toBe(admin.id); + expect(license.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + expect(license.license?.account_id).toBe(account.id); + expect(license.license?.type).toBe(LICENSE_TYPE.NORMAL); + date.setDate(date.getDate() + 1); + } + const newLicense = await selectLicense(source, 6); + expect(newLicense.license?.id).toBe(6); + expect(newLicense.license?.expiry_date).toBe(null); + expect(newLicense.license?.allocated_user_id).toBe(null); + expect(newLicense.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.UNALLOCATED, + ); + const allocatedLicense = await selectLicense(source, 7); + expect(allocatedLicense.license?.id).toBe(7); + expect(allocatedLicense.license?.expiry_date).toEqual(date); + expect(allocatedLicense.license?.allocated_user_id).toBe(admin.id); + expect(allocatedLicense.license?.status).toBe( + LICENSE_ALLOCATED_STATUS.ALLOCATED, + ); + expect(allocatedLicense.license?.account_id).toBe(account.id); + expect(allocatedLicense.license?.type).toBe(LICENSE_TYPE.NORMAL); + } + + const service = module.get(LicensesService); + const context = makeContext('trackingId', 'requestId'); + const response = await service.getAllocatableLicenses( + context, + admin.external_id, + ); + // 既に割り当てられているライセンスは取得されない + // 新規ライセンスは取得される + // 有効期限が長い順に取得される + expect(response.allocatableLicenses.length).toBe(6); + expect(response.allocatableLicenses[0].licenseId).toBe(6); + expect(response.allocatableLicenses[0].expiryDate).toBe(undefined); + expect(response.allocatableLicenses[1].licenseId).toBe(5); // 有効期限が最も長い + expect(response.allocatableLicenses[2].licenseId).toBe(4); + expect(response.allocatableLicenses[3].licenseId).toBe(3); + expect(response.allocatableLicenses[4].licenseId).toBe(2); + expect(response.allocatableLicenses[5].licenseId).toBe(1); + }); +}); diff --git a/dictation_server/src/repositories/licenses/licenses.repository.service.ts b/dictation_server/src/repositories/licenses/licenses.repository.service.ts index 5d20eb4..5b6e873 100644 --- a/dictation_server/src/repositories/licenses/licenses.repository.service.ts +++ b/dictation_server/src/repositories/licenses/licenses.repository.service.ts @@ -525,9 +525,14 @@ export class LicensesRepositoryService { context: Context, myAccountId: number, ): Promise { - const nowDate = new DateWithZeroTime(); const licenseRepo = this.dataSource.getRepository(License); // EntityManagerではorderBy句で、expiry_dateに対して複数条件でソートを使用するため出来ない為、createQueryBuilderを使用する。 + // プロダクト バックログ項目 4218: [FB対応]有効期限当日のライセンスは一覧に表示しない の対応 + // 有効期限が当日のライセンスは取得しない + // 明日の00:00:00を取得 + const tomorrowDate = new DateWithZeroTime( + new Date().setDate(new Date().getDate() + 1), + ); const queryBuilder = licenseRepo .createQueryBuilder('license') .where('license.account_id = :accountId', { accountId: myAccountId }) @@ -538,8 +543,8 @@ export class LicensesRepositoryService { ], }) .andWhere( - '(license.expiry_date >= :nowDate OR license.expiry_date IS NULL)', - { nowDate }, + '(license.expiry_date >= :tomorrowDate OR license.expiry_date IS NULL)', + { tomorrowDate }, ) .comment(`${context.getTrackingId()}_${new Date().toUTCString()}`) .orderBy('license.expiry_date IS NULL', 'DESC') @@ -597,6 +602,9 @@ export class LicensesRepositoryService { } // 期限切れの場合はエラー + // 有効期限が当日のライセンスは割り当て不可 + // XXX 記述は「有効期限が過去のライセンスは割り当て不可」のような意図だと思われるが、実際の処理は「有効期限が当日のライセンスは割り当て不可」になっている + // より正確な記述に修正したほうが良いが、リリース後のため、修正は保留(2024年6月7日) if (targetLicense.expiry_date) { const currentDay = new Date(); currentDay.setHours(23, 59, 59, 999);