diff --git a/dictation_server/src/features/accounts/types/types.ts b/dictation_server/src/features/accounts/types/types.ts index 8aa6179..7bd3623 100644 --- a/dictation_server/src/features/accounts/types/types.ts +++ b/dictation_server/src/features/accounts/types/types.ts @@ -214,9 +214,9 @@ export class LicenseOrder { @ApiProperty({ description: '注文数' }) numberOfOrder: number; @ApiProperty({ description: 'POナンバー' }) - poNumber: String; + poNumber: string; @ApiProperty({ description: '注文状態' }) - status: String; + status: string; } export class GetOrderHistoriesResponce { @ApiProperty({ description: '合計件数' }) diff --git a/dictation_server/src/features/files/test/files.service.mock.ts b/dictation_server/src/features/files/test/files.service.mock.ts index a841f05..b837d5c 100644 --- a/dictation_server/src/features/files/test/files.service.mock.ts +++ b/dictation_server/src/features/files/test/files.service.mock.ts @@ -134,6 +134,8 @@ export const makeDefaultUsersRepositoryMockValue = auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, account: { id: 2, parent_account_id: 2, diff --git a/dictation_server/src/features/files/test/utility.ts b/dictation_server/src/features/files/test/utility.ts index 480becb..a6ccfdc 100644 --- a/dictation_server/src/features/files/test/utility.ts +++ b/dictation_server/src/features/files/test/utility.ts @@ -77,6 +77,8 @@ export const createUser = async ( auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, created_by: 'test_runner', created_at: new Date(), updated_by: 'updater', diff --git a/dictation_server/src/features/licenses/test/liscense.service.mock.ts b/dictation_server/src/features/licenses/test/liscense.service.mock.ts index 58eba66..11058ed 100644 --- a/dictation_server/src/features/licenses/test/liscense.service.mock.ts +++ b/dictation_server/src/features/licenses/test/liscense.service.mock.ts @@ -121,6 +121,8 @@ export const makeDefaultUsersRepositoryMockValue = user1.auto_renew = false; user1.license_alert = false; user1.notification = false; + user1.encryption = false; + user1.prompt = false; user1.deleted_at = undefined; user1.created_by = 'test'; user1.created_at = new Date(); diff --git a/dictation_server/src/features/licenses/test/utility.ts b/dictation_server/src/features/licenses/test/utility.ts index 70cc546..7ff55f0 100644 --- a/dictation_server/src/features/licenses/test/utility.ts +++ b/dictation_server/src/features/licenses/test/utility.ts @@ -44,6 +44,8 @@ export const createUser = async ( auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, created_by: 'test_runner', created_at: new Date(), updated_by: 'updater', diff --git a/dictation_server/src/features/tasks/test/tasks.service.mock.ts b/dictation_server/src/features/tasks/test/tasks.service.mock.ts index 1908047..38a9aba 100644 --- a/dictation_server/src/features/tasks/test/tasks.service.mock.ts +++ b/dictation_server/src/features/tasks/test/tasks.service.mock.ts @@ -428,6 +428,8 @@ const defaultTasksRepositoryMockValue: { auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, created_by: 'test', created_at: new Date(), updated_by: 'test', diff --git a/dictation_server/src/features/tasks/test/utility.ts b/dictation_server/src/features/tasks/test/utility.ts index 5ea37eb..50daad3 100644 --- a/dictation_server/src/features/tasks/test/utility.ts +++ b/dictation_server/src/features/tasks/test/utility.ts @@ -139,6 +139,8 @@ export const createUser = async ( auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, created_by: 'test_runner', created_at: new Date(), updated_by: 'updater', diff --git a/dictation_server/src/features/users/test/users.service.mock.ts b/dictation_server/src/features/users/test/users.service.mock.ts index eebac81..8076a52 100644 --- a/dictation_server/src/features/users/test/users.service.mock.ts +++ b/dictation_server/src/features/users/test/users.service.mock.ts @@ -15,6 +15,7 @@ import { SortDirection, TaskListSortableAttribute, } from '../../../common/types/sort'; +import { AdB2cUser } from '../../../gateways/adb2c/types/types'; export type SortCriteriaRepositoryMockValue = { updateSortCriteria: SortCriteria | Error; @@ -36,6 +37,7 @@ export type AdB2cMockValue = { changePassword: { sub: string } | Error; createUser: string | ConflictError | Error; getUser: Aadb2cUser | Error; + getUsers: AdB2cUser[] | Error; }; export type SendGridMockValue = { @@ -138,8 +140,14 @@ export const makeSendGridServiceMock = (value: SendGridMockValue) => { }; export const makeAdB2cServiceMock = (value: AdB2cMockValue) => { - const { getMetaData, getSignKeySets, changePassword, createUser, getUser } = - value; + const { + getMetaData, + getSignKeySets, + changePassword, + createUser, + getUser, + getUsers, + } = value; return { getMetaData: @@ -172,6 +180,10 @@ export const makeAdB2cServiceMock = (value: AdB2cMockValue) => { : jest .fn, []>() .mockResolvedValue(getUser), + getUsers: + getUsers instanceof Error + ? jest.fn, []>().mockRejectedValue(getUsers) + : jest.fn, []>().mockResolvedValue(getUsers), }; }; @@ -313,6 +325,7 @@ export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => { displayName: 'Hanako Sato', mail: 'hanako@sample.com', }, + getUsers: AdB2cMockUsers, }; }; @@ -333,6 +346,8 @@ export const makeDefaultUsersRepositoryMockValue = user1.auto_renew = false; user1.license_alert = false; user1.notification = false; + user1.encryption = false; + user1.prompt = false; user1.deleted_at = null; user1.created_by = 'test'; user1.created_at = new Date(); @@ -350,6 +365,8 @@ export const makeDefaultUsersRepositoryMockValue = user2.auto_renew = false; user2.license_alert = false; user2.notification = false; + user2.encryption = false; + user2.prompt = false; user2.deleted_at = null; user2.created_by = 'test'; user2.created_at = new Date(); @@ -365,3 +382,39 @@ export const makeDefaultUsersRepositoryMockValue = existsAuthorId: false, }; }; + +const AdB2cMockUsers: AdB2cUser[] = [ + { + id: 'external_id1', + displayName: 'test1', + identities: [ + { + signInType: 'emailAddress', + issuer: 'issuer', + issuerAssignedId: 'test1@mail.com', + }, + ], + }, + { + id: 'external_id2', + displayName: 'test2', + identities: [ + { + signInType: 'emailAddress', + issuer: 'issuer', + issuerAssignedId: 'test2@mail.com', + }, + ], + }, + { + id: 'external_id3', + displayName: 'test3', + identities: [ + { + signInType: 'emailAddress', + issuer: 'issuer', + issuerAssignedId: 'test3@mail.com', + }, + ], + }, +]; diff --git a/dictation_server/src/features/users/test/utility.ts b/dictation_server/src/features/users/test/utility.ts new file mode 100644 index 0000000..6d2251a --- /dev/null +++ b/dictation_server/src/features/users/test/utility.ts @@ -0,0 +1,205 @@ +import { DataSource } from 'typeorm'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigModule } from '@nestjs/config'; +import { UserGroupsRepositoryModule } from '../../../repositories/user_groups/user_groups.repository.module'; +import { TasksRepositoryModule } from '../../../repositories/tasks/tasks.repository.module'; +import { AuthModule } from '../../../features/auth/auth.module'; +import { AdB2cModule } from '../../../gateways/adb2c/adb2c.module'; +import { AccountsModule } from '../../../features/accounts/accounts.module'; +import { UsersModule } from '../../../features/users/users.module'; +import { FilesModule } from '../../../features/files/files.module'; +import { TasksModule } from '../../../features/tasks/tasks.module'; +import { SendGridModule } from '../../../features/../gateways/sendgrid/sendgrid.module'; +import { LicensesModule } from '../../../features/licenses/licenses.module'; +import { AccountsRepositoryModule } from '../../../repositories/accounts/accounts.repository.module'; +import { UsersRepositoryModule } from '../../../repositories/users/users.repository.module'; +import { LicensesRepositoryModule } from '../../../repositories/licenses/licenses.repository.module'; +import { AudioFilesRepositoryModule } from '../../../repositories/audio_files/audio_files.repository.module'; +import { AudioOptionItemsRepositoryModule } from '../../../repositories/audio_option_items/audio_option_items.repository.module'; +import { CheckoutPermissionsRepositoryModule } from '../../../repositories/checkout_permissions/checkout_permissions.repository.module'; +import { NotificationModule } from '../../../features//notification/notification.module'; +import { NotificationhubModule } from '../../../gateways/notificationhub/notificationhub.module'; +import { BlobstorageModule } from '../../../gateways/blobstorage/blobstorage.module'; +import { AuthGuardsModule } from '../../../common/guards/auth/authguards.module'; +import { SortCriteriaRepositoryModule } from '../../../repositories/sort_criteria/sort_criteria.repository.module'; +import { AuthService } from '../../../features/auth/auth.service'; +import { AccountsService } from '../../../features/accounts/accounts.service'; +import { UsersService } from '../../../features/users/users.service'; +import { NotificationhubService } from '../../../gateways/notificationhub/notificationhub.service'; +import { FilesService } from '../../../features/files/files.service'; +import { LicensesService } from '../../../features/licenses/licenses.service'; +import { TasksService } from '../../../features/tasks/tasks.service'; +import { User } from '../../../repositories/users/entity/user.entity'; +import { Account } from '../../../repositories/accounts/entity/account.entity'; +import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity'; +import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity'; +import { License } from '../../../repositories/licenses/entity/license.entity'; +import { AdB2cMockValue, makeAdB2cServiceMock } from './users.service.mock'; +import { AdB2cService } from '../../../gateways/adb2c/adb2c.service'; +import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../../constants'; + +export const createAccount = async ( + datasource: DataSource, +): Promise<{ accountId: number }> => { + const { identifiers } = await datasource.getRepository(Account).insert({ + tier: 1, + country: 'JP', + delegation_permission: false, + locked: false, + company_name: 'test inc.', + verified: true, + deleted_at: '', + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + const account = identifiers.pop() as Account; + return { accountId: account.id }; +}; + +export const createUser = async ( + datasource: DataSource, + accountId: number, + external_id: string, + role: string, + author_id?: string | undefined, + auto_renew?: boolean, +): Promise<{ userId: number; externalId: string }> => { + const { identifiers } = await datasource.getRepository(User).insert({ + account_id: accountId, + external_id: external_id, + role: role, + accepted_terms_version: '1.0', + author_id: author_id, + email_verified: true, + auto_renew: auto_renew, + license_alert: true, + notification: true, + encryption: false, + prompt: false, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + const user = identifiers.pop() as User; + return { userId: user.id, externalId: external_id }; +}; + +/** + * + * @param datasource + * @param account_id + * @param user_group_name + * @param user_id + * @returns + */ +export const createUserGroup = async ( + datasource: DataSource, + account_id: number, + user_group_name: string, + user_id: number[], +): Promise<{ userGroupId: number }> => { + const { identifiers: userGroupIdentifiers } = await datasource + .getRepository(UserGroup) + .insert({ + account_id: account_id, + name: user_group_name, + created_by: 'test', + updated_by: 'test', + }); + const userGroup = userGroupIdentifiers.pop() as UserGroup; + const userGroupMenber = user_id.map((id) => { + return { + user_group_id: userGroup.id, + user_id: id, + created_by: 'test', + updated_by: 'test', + }; + }); + await datasource.getRepository(UserGroupMember).save(userGroupMenber); + + return { userGroupId: userGroup.id }; +}; + +export const createLicense = async ( + datasource: DataSource, + accountId: number, + userId?: number, + expiry_date?: Date, +): Promise => { + const { identifiers } = await datasource.getRepository(License).insert({ + account_id: accountId, + type: LICENSE_TYPE.NORMAL, + status: LICENSE_ALLOCATED_STATUS.ALLOCATED, + allocated_user_id: userId, + expiry_date: expiry_date, + created_by: 'test_runner', + created_at: new Date(), + updated_by: 'updater', + updated_at: new Date(), + }); + identifiers.pop() as License; +}; + +export const makeTestingModuleWithAdb2c = async ( + datasource: DataSource, + adB2cMockValue: AdB2cMockValue, +): Promise => { + try { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + envFilePath: ['.env.local', '.env'], + isGlobal: true, + }), + AuthModule, + AdB2cModule, + AccountsModule, + UsersModule, + FilesModule, + TasksModule, + UsersModule, + SendGridModule, + LicensesModule, + AccountsRepositoryModule, + UsersRepositoryModule, + LicensesRepositoryModule, + AudioFilesRepositoryModule, + AudioOptionItemsRepositoryModule, + TasksRepositoryModule, + CheckoutPermissionsRepositoryModule, + UserGroupsRepositoryModule, + UserGroupsRepositoryModule, + NotificationModule, + NotificationhubModule, + BlobstorageModule, + AuthGuardsModule, + SortCriteriaRepositoryModule, + ], + providers: [ + AuthService, + AccountsService, + UsersService, + NotificationhubService, + FilesService, + TasksService, + LicensesService, + ], + }) + .useMocker(async (token) => { + switch (token) { + case DataSource: + return datasource; + } + }) + .overrideProvider(AdB2cService) + .useValue(makeAdB2cServiceMock(adB2cMockValue)) + .compile(); + + return module; + } catch (e) { + console.log(e); + } +}; diff --git a/dictation_server/src/features/users/users.controller.ts b/dictation_server/src/features/users/users.controller.ts index ffb6bcd..9a31882 100644 --- a/dictation_server/src/features/users/users.controller.ts +++ b/dictation_server/src/features/users/users.controller.ts @@ -115,12 +115,10 @@ export class UsersController { @UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] })) @Get() async getUsers(@Req() req: Request): Promise { - console.log(req.header('Authorization')); - const accessToken = retrieveAuthorizationToken(req); const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken; - const users = await this.usersService.getUsers(decodedToken); + const users = await this.usersService.getUsers(decodedToken.userId); return { users }; } diff --git a/dictation_server/src/features/users/users.service.spec.ts b/dictation_server/src/features/users/users.service.spec.ts index 252effc..7d01d17 100644 --- a/dictation_server/src/features/users/users.service.spec.ts +++ b/dictation_server/src/features/users/users.service.spec.ts @@ -1,7 +1,6 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { AccessToken } from '../../common/token'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; -import { User as EntityUser } from '../../repositories/users/entity/user.entity'; import { makeDefaultAdB2cMockValue, makeDefaultConfigValue, @@ -10,10 +9,22 @@ import { makeDefaultUsersRepositoryMockValue, makeUsersServiceMock, } from './test/users.service.mock'; -import { User } from './types/types'; import { EmailAlreadyVerifiedError } from '../../repositories/users/errors/types'; +import { + createAccount, + createLicense, + createUser, + createUserGroup, + makeTestingModuleWithAdb2c, +} from './test/utility'; +import { DataSource } from 'typeorm'; +import { UsersService } from './users.service'; +import { + LICENSE_EXPIRATION_THRESHOLD_DAYS, + USER_LICENSE_STATUS, +} from '../../constants'; -describe('UsersService', () => { +describe('UsersService.confirmUser', () => { it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになる', async () => { const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); const adb2cParam = makeDefaultAdB2cMockValue(); @@ -49,6 +60,8 @@ describe('UsersService', () => { auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, }; const adb2cParam = makeDefaultAdB2cMockValue(); const configMockValue = makeDefaultConfigValue(); @@ -104,6 +117,8 @@ describe('UsersService', () => { auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, }; const adb2cParam = makeDefaultAdB2cMockValue(); const sendGridMockValue = makeDefaultSendGridlValue(); @@ -162,6 +177,8 @@ describe('UsersService', () => { auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, }; const adb2cParam = makeDefaultAdB2cMockValue(); const sendGridMockValue = makeDefaultSendGridlValue(); @@ -225,6 +242,8 @@ describe('UsersService', () => { auto_renew: true, license_alert: true, notification: true, + encryption: false, + prompt: false, }; const adb2cParam = makeDefaultAdB2cMockValue(); const sendGridMockValue = makeDefaultSendGridlValue(); @@ -283,7 +302,7 @@ describe('UsersService', () => { }); }); -describe('UsersService', () => { +describe('UsersService.createUser', () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author)', async () => { const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); const adb2cParam = makeDefaultAdB2cMockValue(); @@ -319,9 +338,7 @@ describe('UsersService', () => { ), ).toEqual(undefined); }); -}); -describe('UsersService', () => { it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => { const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); const adb2cParam = makeDefaultAdB2cMockValue(); @@ -358,485 +375,637 @@ describe('UsersService', () => { ), ).toEqual(undefined); }); + it('DBネットワークエラーとなる場合、エラーとなる。', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + usersRepositoryMockValue.createNormalUser = new Error('DB error'); + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + const name = 'test_user5'; + const role = 'Transcriptioninst'; + const email = 'test5@example.co.jp'; + const autoRenew = true; + const licenseAlert = true; + const notification = true; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + await expect( + service.createUser( + token, + name, + role, + email, + autoRenew, + licenseAlert, + notification, + ), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.createUser = new Error(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + const name = 'test_user6'; + const role = 'Transcriptioninst'; + const email = 'test6@example.co.jp'; + const autoRenew = true; + const licenseAlert = true; + const notification = true; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + await expect( + service.createUser( + token, + name, + role, + email, + autoRenew, + licenseAlert, + notification, + ), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('メールアドレスが重複している場合、エラーとなる。', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.createUser = { reason: 'email', message: 'ObjectConflict' }; + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + const name = 'test_user7'; + const role = 'Transcriptioninst'; + const email = 'test7@example.co.jp'; + const autoRenew = true; + const licenseAlert = true; + const notification = true; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + await expect( + service.createUser( + token, + name, + role, + email, + autoRenew, + licenseAlert, + notification, + ), + ).rejects.toEqual( + new HttpException(makeErrorResponse('E010301'), HttpStatus.BAD_REQUEST), + ); + }); + it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + usersRepositoryMockValue.createNormalUser = new Error(); + usersRepositoryMockValue.existsAuthorId = true; + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + const name = 'test_user8'; + const role = 'Author'; + const email = 'test8@example.co.jp'; + const autoRenew = true; + const licenseAlert = true; + const notification = true; + const authorId = 'testID'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + await expect( + service.createUser( + token, + name, + role, + email, + autoRenew, + licenseAlert, + notification, + authorId, + ), + ).rejects.toEqual( + new HttpException(makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST), + ); + }); + it('AuthorIDが重複している場合、エラーとなる。(insert失敗)', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + usersRepositoryMockValue.createNormalUser = new Error(); + usersRepositoryMockValue.createNormalUser.name = 'ER_DUP_ENTRY'; + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + const name = 'test_user9'; + const role = 'Author'; + const email = 'test9@example.co.jp'; + const autoRenew = true; + const licenseAlert = true; + const notification = true; + const authorId = 'testID'; + const token: AccessToken = { userId: '0001', role: '', tier: 5 }; + await expect( + service.createUser( + token, + name, + role, + email, + autoRenew, + licenseAlert, + notification, + authorId, + ), + ).rejects.toEqual( + new HttpException(makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST), + ); + }); }); -it('DBネットワークエラーとなる場合、エラーとなる。', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - usersRepositoryMockValue.createNormalUser = new Error('DB error'); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - const name = 'test_user5'; - const role = 'Transcriptioninst'; - const email = 'test5@example.co.jp'; - const autoRenew = true; - const licenseAlert = true; - const notification = true; - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; - await expect( - service.createUser( - token, - name, - role, - email, - autoRenew, - licenseAlert, - notification, - ), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); -it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - adb2cParam.createUser = new Error(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - const name = 'test_user6'; - const role = 'Transcriptioninst'; - const email = 'test6@example.co.jp'; - const autoRenew = true; - const licenseAlert = true; - const notification = true; - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; - await expect( - service.createUser( - token, - name, - role, - email, - autoRenew, - licenseAlert, - notification, - ), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); -it('メールアドレスが重複している場合、エラーとなる。', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - adb2cParam.createUser = { reason: 'email', message: 'ObjectConflict' }; - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - const name = 'test_user7'; - const role = 'Transcriptioninst'; - const email = 'test7@example.co.jp'; - const autoRenew = true; - const licenseAlert = true; - const notification = true; - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; - await expect( - service.createUser( - token, - name, - role, - email, - autoRenew, - licenseAlert, - notification, - ), - ).rejects.toEqual( - new HttpException(makeErrorResponse('E010301'), HttpStatus.BAD_REQUEST), - ); -}); -it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - usersRepositoryMockValue.createNormalUser = new Error(); - usersRepositoryMockValue.existsAuthorId = true; +describe('UsersService.getUsers', () => { + let source: DataSource = null; + beforeEach(async () => { + source = new DataSource({ + type: 'sqlite', + database: ':memory:', + logging: false, + entities: [__dirname + '/../../**/*.entity{.ts,.js}'], + synchronize: true, // trueにすると自動的にmigrationが行われるため注意 + }); + return source.initialize(); + }); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - const name = 'test_user8'; - const role = 'Author'; - const email = 'test8@example.co.jp'; - const autoRenew = true; - const licenseAlert = true; - const notification = true; - const authorId = 'testID'; - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; - await expect( - service.createUser( - token, - name, - role, - email, - autoRenew, - licenseAlert, - notification, - authorId, - ), - ).rejects.toEqual( - new HttpException(makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST), - ); -}); -it('AuthorIDが重複している場合、エラーとなる。(insert失敗)', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - usersRepositoryMockValue.createNormalUser = new Error(); - usersRepositoryMockValue.createNormalUser.name = 'ER_DUP_ENTRY'; + afterEach(async () => { + await source.destroy(); + source = null; + }); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - const name = 'test_user9'; - const role = 'Author'; - const email = 'test9@example.co.jp'; - const autoRenew = true; - const licenseAlert = true; - const notification = true; - const authorId = 'testID'; - const token: AccessToken = { userId: '0001', role: '', tier: 5 }; - await expect( - service.createUser( - token, - name, - role, - email, - autoRenew, - licenseAlert, - notification, - authorId, - ), - ).rejects.toEqual( - new HttpException(makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST), - ); + it('ユーザーの一覧を取得できる(ライセンス未割当)', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const module = await makeTestingModuleWithAdb2c(source, adb2cParam); + + const { accountId } = await createAccount(source); + const { externalId: externalId_author } = await createUser( + source, + accountId, + 'external_id1', + 'author', + 'AUTHOR_ID', + true, + ); + + const { userId: typistUser } = await createUser( + source, + accountId, + 'external_id2', + 'typist', + undefined, + true, + ); + + await createUserGroup(source, accountId, 'group1', [typistUser]); + + await createUser( + source, + accountId, + 'external_id3', + 'none', + undefined, + true, + ); + + const service = module.get(UsersService); + + const expectedUsers = [ + { + name: 'test1', + role: 'author', + authorId: 'AUTHOR_ID', + typistGroupName: [], + email: 'test1@mail.com', + emailVerified: true, + autoRenew: true, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: undefined, + remaining: undefined, + licenseStatus: USER_LICENSE_STATUS.NO_LICENSE, + }, + { + name: 'test2', + role: 'typist', + authorId: undefined, + typistGroupName: ['group1'], + email: 'test2@mail.com', + emailVerified: true, + autoRenew: true, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: undefined, + remaining: undefined, + licenseStatus: USER_LICENSE_STATUS.NO_LICENSE, + }, + { + name: 'test3', + role: 'none', + authorId: undefined, + typistGroupName: [], + email: 'test3@mail.com', + emailVerified: true, + autoRenew: true, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: undefined, + remaining: undefined, + licenseStatus: USER_LICENSE_STATUS.NO_LICENSE, + }, + ]; + + expect(await service.getUsers(externalId_author)).toEqual(expectedUsers); + }); + + it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const module = await makeTestingModuleWithAdb2c(source, adb2cParam); + + const { accountId } = await createAccount(source); + const { userId: user1, externalId: external_id1 } = await createUser( + source, + accountId, + 'external_id1', + 'author', + 'AUTHOR_ID1', + true, + ); + const { userId: user2 } = await createUser( + source, + accountId, + 'external_id2', + 'author', + 'AUTHOR_ID2', + true, + ); + const { userId: user3 } = await createUser( + source, + accountId, + 'external_id3', + 'author', + 'AUTHOR_ID3', + false, + ); + + const date1 = new Date(); + date1.setDate(date1.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS + 1); + const date2 = new Date(); + date2.setDate(date2.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS); + const date3 = new Date(); + date3.setDate(date3.getDate() + LICENSE_EXPIRATION_THRESHOLD_DAYS - 1); + await createLicense(source, accountId, user1, date1); + await createLicense(source, accountId, user2, date2); + await createLicense(source, accountId, user3, date3); + + const service = module.get(UsersService); + + const expectedUsers = [ + { + name: 'test1', + role: 'author', + authorId: 'AUTHOR_ID1', + typistGroupName: [], + email: 'test1@mail.com', + emailVerified: true, + autoRenew: true, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: `${date1.getFullYear()}/${ + date1.getMonth() + 1 + }/${date1.getDate()}`, + remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS + 1, + licenseStatus: USER_LICENSE_STATUS.NORMAL, + }, + { + name: 'test2', + role: 'author', + authorId: 'AUTHOR_ID2', + typistGroupName: [], + email: 'test2@mail.com', + emailVerified: true, + autoRenew: true, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: `${date2.getFullYear()}/${ + date2.getMonth() + 1 + }/${date2.getDate()}`, + remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS, + licenseStatus: USER_LICENSE_STATUS.RENEW, + }, + { + name: 'test3', + role: 'author', + authorId: 'AUTHOR_ID3', + typistGroupName: [], + email: 'test3@mail.com', + emailVerified: true, + autoRenew: false, + licenseAlert: true, + notification: true, + encryption: false, + prompt: false, + expiration: `${date3.getFullYear()}/${ + date3.getMonth() + 1 + }/${date3.getDate()}`, + remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS - 1, + licenseStatus: USER_LICENSE_STATUS.ALERT, + }, + ]; + + expect(await service.getUsers(external_id1)).toEqual(expectedUsers); + }); + + it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const module = await makeTestingModuleWithAdb2c(source, adb2cParam); + + const { accountId } = await createAccount(source); + await createUser( + source, + accountId, + 'external_id1', + 'author', + 'AUTHOR_ID', + true, + ); + + const service = module.get(UsersService); + await expect(service.getUsers('externalId_failed')).rejects.toEqual( + new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND), + ); + }); + + it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.getUsers = new Error('ADB2C error'); + const module = await makeTestingModuleWithAdb2c(source, adb2cParam); + + const { accountId } = await createAccount(source); + const { externalId: externalId_author } = await createUser( + source, + accountId, + 'external_id1', + 'author', + 'AUTHOR_ID', + true, + ); + + const service = module.get(UsersService); + await expect(service.getUsers(externalId_author)).rejects.toEqual( + new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND), + ); + }); }); -it('ユーザの一覧を取得する', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); +describe('UsersService.updateSortCriteria', () => { + it('ソート条件を変更できる', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); - expect( - await service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }), - ).toEqual(expectedUsers); + expect( + await service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).toEqual(undefined); + }); + + it('ユーザー情報が存在せず、ソート条件を変更できない', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + + usersRepositoryMockValue.findUserByExternalId = new Error('user not found'); + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + + await expect( + service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('ソート条件が存在せず、ソート条件を変更できない', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + sortCriteriaRepositoryMockValue.updateSortCriteria = new Error( + 'sort criteria not found', + ); + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + + await expect( + service.updateSortCriteria('AUTHOR_ID', 'ASC', { + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); }); -const expectedUsers = [ - { - name: 'Hanako Sato', - role: 'none', - authorId: '6cce347f-0cf1-a15e-19ab-d00988b643f9', - typistGroupName: [''], - email: 'hanako@sample.com', - emailVerified: true, - autoRenew: false, - licenseAlert: false, - notification: false, - encryption: false, - prompt: false, - licenseStatus: 'NoLicense', - }, - { - name: 'Hanako Sato', - role: 'none', - authorId: '551c4077-5b55-a38c-2c55-cd1edd537aa8', - typistGroupName: [''], - email: 'hanako@sample.com', - emailVerified: true, - autoRenew: false, - licenseAlert: false, - notification: false, - encryption: false, - prompt: false, - licenseStatus: 'NoLicense', - }, -]; +describe('UsersService.getSortCriteria', () => { + it('ソート条件を取得できる', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); -it('ユーザの一覧を取得に失敗する', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - usersRepositoryMockValue.findSameAccountUsers = new Error( - 'Failure to acquire', - ); + expect( + await service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' }); + }); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); + it('ソート条件が存在せず、ソート条件を取得できない', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); - await expect( - service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }), - ).rejects.toEqual( - new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND), - ); -}); - -it('ユーザの一覧を0件取得する', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - - // モックでDBからのユーザ取得を空にする - const noDbUsers: EntityUser[] = []; - usersRepositoryMockValue.findSameAccountUsers = noDbUsers; - - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - const emptyMergedUsers: User[] = []; - expect( - await service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }), - ).toEqual(emptyMergedUsers); -}); - -it('ソート条件を変更できる', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - expect( - await service.updateSortCriteria('AUTHOR_ID', 'ASC', { - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).toEqual(undefined); -}); - -it('ユーザー情報が存在せず、ソート条件を変更できない', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - - usersRepositoryMockValue.findUserByExternalId = new Error('user not found'); - - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - await expect( - service.updateSortCriteria('AUTHOR_ID', 'ASC', { - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); - -it('ソート条件が存在せず、ソート条件を変更できない', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - sortCriteriaRepositoryMockValue.updateSortCriteria = new Error( - 'sort criteria not found', - ); - - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - await expect( - service.updateSortCriteria('AUTHOR_ID', 'ASC', { - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); - -it('ソート条件を取得できる', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - expect( - await service.getSortCriteria({ - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' }); -}); - -it('ソート条件が存在せず、ソート条件を取得できない', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - - sortCriteriaRepositoryMockValue.getSortCriteria = new Error( - 'sort criteria not found', - ); - - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - await expect( - service.getSortCriteria({ - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); -}); - -it('DBから取得した値が不正だった場合、エラーとなる', async () => { - const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); - const adb2cParam = makeDefaultAdB2cMockValue(); - const sendgridMockValue = makeDefaultSendGridlValue(); - const configMockValue = makeDefaultConfigValue(); - const sortCriteriaRepositoryMockValue = - makeDefaultSortCriteriaRepositoryMockValue(); - sortCriteriaRepositoryMockValue.getSortCriteria = { - id: 1, - direction: 'AAA', - parameter: 'BBBBB', - user_id: 1, - }; - - const service = await makeUsersServiceMock( - usersRepositoryMockValue, - adb2cParam, - sendgridMockValue, - configMockValue, - sortCriteriaRepositoryMockValue, - ); - - await expect( - service.getSortCriteria({ - role: 'none admin', - userId: 'xxxxxxxxxxxx', - tier: 5, - }), - ).rejects.toEqual( - new HttpException( - makeErrorResponse('E009999'), - HttpStatus.INTERNAL_SERVER_ERROR, - ), - ); + sortCriteriaRepositoryMockValue.getSortCriteria = new Error( + 'sort criteria not found', + ); + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + + await expect( + service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('DBから取得した値が不正だった場合、エラーとなる', async () => { + const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue(); + const adb2cParam = makeDefaultAdB2cMockValue(); + const sendgridMockValue = makeDefaultSendGridlValue(); + const configMockValue = makeDefaultConfigValue(); + const sortCriteriaRepositoryMockValue = + makeDefaultSortCriteriaRepositoryMockValue(); + sortCriteriaRepositoryMockValue.getSortCriteria = { + id: 1, + direction: 'AAA', + parameter: 'BBBBB', + user_id: 1, + }; + + const service = await makeUsersServiceMock( + usersRepositoryMockValue, + adb2cParam, + sendgridMockValue, + configMockValue, + sortCriteriaRepositoryMockValue, + ); + + await expect( + service.getSortCriteria({ + role: 'none admin', + userId: 'xxxxxxxxxxxx', + tier: 5, + }), + ).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); }); diff --git a/dictation_server/src/features/users/users.service.ts b/dictation_server/src/features/users/users.service.ts index 9a157b2..d816a99 100644 --- a/dictation_server/src/features/users/users.service.ts +++ b/dictation_server/src/features/users/users.service.ts @@ -22,7 +22,11 @@ import { User as EntityUser } from '../../repositories/users/entity/user.entity' import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { GetRelationsResponse, User } from './types/types'; import { EmailAlreadyVerifiedError } from '../../repositories/users/errors/types'; -import { USER_LICENSE_STATUS } from '../../constants'; +import { + LICENSE_EXPIRATION_THRESHOLD_DAYS, + USER_LICENSE_STATUS, +} from '../../constants'; +import { DateWithZeroTime } from '../licenses/types/types'; @Injectable() export class UsersService { @@ -293,41 +297,79 @@ export class UsersService { * @param accessToken * @returns users */ - async getUsers(accessToken: AccessToken): Promise { + async getUsers(externalId: string): Promise { this.logger.log(`[IN] ${this.getUsers.name}`); try { // DBから同一アカウントのユーザ一覧を取得する const dbUsers = await this.usersRepository.findSameAccountUsers( - accessToken.userId, + externalId, ); - // 値をマージして定義されたレスポンス通りに返す - const users: User[] = []; - // TODO [Task2002] 膨大なループが発生することが見込まれ商用には耐えないので、本実装時に修正予定 - for (let i = 0; i < dbUsers.length; i++) { - // Azure AD B2Cからユーザーを取得する - const aadb2cUser = await this.adB2cService.getUser( - dbUsers[i].external_id, - ); + // DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する + const externalIds = dbUsers.map((x) => x.external_id); + const adb2cUsers = await this.adB2cService.getUsers(externalIds); + + // DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出 + const users = dbUsers.map((x) => { + // ユーザーの所属グループ名を取得する + const groupNames = + x.userGroupMembers?.map((group) => group.userGroup?.name) ?? []; + + const adb2cUser = adb2cUsers.find((user) => user.id === x.external_id); + + // メールアドレスを取得する + const mail = adb2cUser.identities.find( + (identity) => identity.signInType === 'emailAddress', + ).issuerAssignedId; + + let status = USER_LICENSE_STATUS.NORMAL; + + // ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する + // ライセンスが存在しない場合は、undefinedのままとする + let expiration: string | undefined = undefined; + let remaining: number | undefined = undefined; + + if (x.license) { + // 有効期限日付 YYYY/MM/DD + const expiry_date = x.license.expiry_date; + expiration = `${expiry_date.getFullYear()}/${ + expiry_date.getMonth() + 1 + }/${expiry_date.getDate()}`; + + const currentDate = new DateWithZeroTime(); + // 有効期限までの日数 + remaining = Math.floor( + (expiry_date.getTime() - currentDate.getTime()) / + (1000 * 60 * 60 * 24), + ); + if (remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS) { + status = x.auto_renew + ? USER_LICENSE_STATUS.RENEW + : USER_LICENSE_STATUS.ALERT; + } + } else { + status = USER_LICENSE_STATUS.NO_LICENSE; + } + + return { + name: adb2cUser.displayName, + role: x.role, + authorId: x.author_id ?? undefined, + typistGroupName: groupNames, + email: mail, + emailVerified: x.email_verified, + autoRenew: x.auto_renew, + licenseAlert: x.license_alert, + notification: x.notification, + encryption: x.encryption, + prompt: x.prompt, + expiration: expiration, + remaining: remaining, + licenseStatus: status, + }; + }); - const user = new User(); - user.name = aadb2cUser.displayName; - user.role = dbUsers[i].role; - user.authorId = dbUsers[i].author_id; - // TODO [Task2247] 将来的にはDBから取得できるようになる想定のため暫定的にtypistGroupNameに仮の値を設定 - user.typistGroupName = ['']; - user.email = aadb2cUser.mail; - user.emailVerified = dbUsers[i].email_verified; - user.autoRenew = dbUsers[i].auto_renew; - user.licenseAlert = dbUsers[i].license_alert; - user.notification = dbUsers[i].notification; - // TODO: 仮の値を入れておく - user.encryption = false; - user.prompt = false; - user.licenseStatus = USER_LICENSE_STATUS.NO_LICENSE; - users.push(user); - } return users; } catch (e) { this.logger.error(`error=${e}`); diff --git a/dictation_server/src/gateways/adb2c/adb2c.service.ts b/dictation_server/src/gateways/adb2c/adb2c.service.ts index 634fa7e..194d59c 100644 --- a/dictation_server/src/gateways/adb2c/adb2c.service.ts +++ b/dictation_server/src/gateways/adb2c/adb2c.service.ts @@ -206,9 +206,10 @@ export class AdB2cService { const element = chunkExternalIds[index]; const res: AdB2cResponse = await this.graphClient .api(`users/`) - .select(['id', 'displayName']) + .select(['id', 'displayName', 'identities']) .filter(`id in (${element.map((y) => `'${y}'`).join(',')})`) .get(); + b2cUsers.push(...res.value); } diff --git a/dictation_server/src/gateways/adb2c/types/types.ts b/dictation_server/src/gateways/adb2c/types/types.ts index 1eb845a..a7261ef 100644 --- a/dictation_server/src/gateways/adb2c/types/types.ts +++ b/dictation_server/src/gateways/adb2c/types/types.ts @@ -2,4 +2,14 @@ export type AdB2cResponse = { '@odata.context': string; value: AdB2cUser[]; }; -export type AdB2cUser = { id: string; displayName: string }; +export type AdB2cUser = { + id: string; + displayName: string; + identities?: UserIdentity[]; +}; + +export type UserIdentity = { + signInType: string; + issuer: string; + issuerAssignedId: string; +}; diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index 8bbeb4d..301b0ff 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -4,7 +4,10 @@ import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, + OneToOne, + JoinColumn, } from 'typeorm'; +import { User } from '../../users/entity/user.entity'; @Entity({ name: 'license_orders' }) export class LicenseOrder { @@ -88,6 +91,10 @@ export class License { @UpdateDateColumn() updated_at: Date; + + @OneToOne(() => User, (user) => user.license) + @JoinColumn({ name: 'allocated_user_id' }) + user?: User; } @Entity({ name: 'licenses_history' }) export class LicenseHistory { diff --git a/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts b/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts index d79aaef..2745334 100644 --- a/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts +++ b/dictation_server/src/repositories/user_groups/entity/user_group.entity.ts @@ -1,4 +1,5 @@ -import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; +import { UserGroupMember } from './user_group_member.entity'; @Entity({ name: 'user_group' }) export class UserGroup { @@ -25,4 +26,10 @@ export class UserGroup { @Column({ nullable: true }) updated_at?: Date; + + @OneToMany( + () => UserGroupMember, + (userGroupMember) => userGroupMember.userGroup, + ) + userGroupMembers?: UserGroupMember[]; } diff --git a/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts index d010434..9256d0b 100644 --- a/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts +++ b/dictation_server/src/repositories/user_groups/entity/user_group_member.entity.ts @@ -3,9 +3,10 @@ import { Entity, Column, PrimaryGeneratedColumn, - OneToOne, JoinColumn, + ManyToOne, } from 'typeorm'; +import { UserGroup } from './user_group.entity'; @Entity({ name: 'user_group_member' }) export class UserGroupMember { @@ -33,7 +34,11 @@ export class UserGroupMember { @Column({ nullable: true }) updated_at?: Date; - @OneToOne(() => User, (user) => user.id) + @ManyToOne(() => User, (user) => user.id) @JoinColumn({ name: 'user_id' }) user?: User; + + @ManyToOne(() => UserGroup, (userGroup) => userGroup.id) + @JoinColumn({ name: 'user_group_id' }) + userGroup?: UserGroup; } diff --git a/dictation_server/src/repositories/users/entity/user.entity.ts b/dictation_server/src/repositories/users/entity/user.entity.ts index 4ee30df..f42123e 100644 --- a/dictation_server/src/repositories/users/entity/user.entity.ts +++ b/dictation_server/src/repositories/users/entity/user.entity.ts @@ -7,7 +7,11 @@ import { UpdateDateColumn, ManyToOne, JoinColumn, + OneToOne, + OneToMany, } from 'typeorm'; +import { License } from '../../licenses/entity/license.entity'; +import { UserGroupMember } from '../../user_groups/entity/user_group_member.entity'; @Entity({ name: 'users' }) export class User { @@ -41,6 +45,12 @@ export class User { @Column() notification: boolean; + @Column() + encryption: boolean; + + @Column() + prompt: boolean; + @Column({ nullable: true }) deleted_at?: Date; @@ -59,4 +69,10 @@ export class User { @ManyToOne(() => Account, (account) => account.user) @JoinColumn({ name: 'account_id' }) account?: Account; + + @OneToOne(() => License, (license) => license.user) + license?: License; + + @OneToMany(() => UserGroupMember, (userGroupMember) => userGroupMember.user) + userGroupMembers?: UserGroupMember[]; } diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index cc7cb61..b37feb5 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -203,21 +203,28 @@ export class UsersRepositoryService { /** * 同じアカウントIDを持つユーザーを探す - * @param criteria + * @param externalId * @returns User[] */ - async findSameAccountUsers(criteria: string): Promise { - const dbUser = await this.dataSource.getRepository(User).findOne({ - where: { - external_id: criteria, - }, + async findSameAccountUsers(external_id: string): Promise { + return await this.dataSource.transaction(async (entityManager) => { + const repo = entityManager.getRepository(User); + + const accountId = (await repo.findOne({ where: { external_id } })) + .account_id; + + const dbUsers = await this.dataSource.getRepository(User).find({ + relations: { + userGroupMembers: { + userGroup: true, + }, + license: true, + }, + where: { account_id: accountId }, + }); + + return dbUsers; }); - - const dbUsers = await this.dataSource - .getRepository(User) - .find({ where: { account_id: dbUser.account_id } }); - - return dbUsers; } /**