Skip to content

Commit

Permalink
feat: 그룹 수정 커맨드 및 관련 이벤트 구현 (#42)
Browse files Browse the repository at this point in the history
* feat: 그룹 수정 커맨드 및 관련 이벤트 구현

* fix: 그룹 수정 시 중단 된 그룹을 진행 중으로 변경하도록 수정

* test: 누락된 테스트 추가

* test: 랜덤하게 열거형을 처리하면서 테스트가 깨지는 문제 해결
  • Loading branch information
Coalery authored Dec 11, 2023
1 parent 5687e2b commit 5323f7a
Show file tree
Hide file tree
Showing 28 changed files with 1,008 additions and 236 deletions.
22 changes: 12 additions & 10 deletions src/__test__/fixtures/domain/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@ import {
Group,
GroupConstructorParams,
} from '@sight/app/domain/group/model/Group';
import {
GroupLog,
GroupLogConstructorParams,
} from '@sight/app/domain/group/model/GroupLog';
import {
GroupMember,
GroupMemberConstructorParams,
} from '@sight/app/domain/group/model/GroupMember';
import {
GroupInterest,
GroupInterestConstructorParams,
} from '@sight/app/domain/interest/model/GroupInterest';

export function generateGroup(params?: Partial<GroupConstructorParams>): Group {
return new Group({
id: faker.string.uuid(),
category: faker.helpers.enumValue(GroupCategory),
state: faker.helpers.enumValue(GroupState),
state: GroupState.PROGRESS,
title: faker.lorem.word(),
authorUserId: faker.string.uuid(),
adminUserId: faker.string.uuid(),
purpose: faker.lorem.sentence(),
interestIds: [faker.string.uuid()],
technology: [faker.lorem.word()],
grade: faker.helpers.enumValue(GroupAccessGrade),
lastUpdaterUserId: faker.string.uuid(),
Expand All @@ -51,13 +52,14 @@ export function generateGroupMember(
});
}

export function generateGroupInterest(
params?: Partial<GroupInterestConstructorParams>,
): GroupInterest {
return new GroupInterest({
export function generateGroupLog(
params?: Partial<GroupLogConstructorParams>,
): GroupLog {
return new GroupLog({
id: faker.string.uuid(),
interestId: faker.string.uuid(),
userId: faker.string.uuid(),
groupId: faker.string.uuid(),
message: faker.lorem.sentence(),
createdAt: faker.date.anytime(),
...params,
});
Expand Down
78 changes: 73 additions & 5 deletions src/app/application/group/authorizer/GroupAuthorizer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('GroupAuthorizer', () => {
clear();
});

describe('createGroup', () => {
describe('authorizeForCreateGroup', () => {
let user: User;

describe('운영진 유저라면', () => {
Expand All @@ -43,7 +43,7 @@ describe('GroupAuthorizer', () => {
'%s 유형의 그룹을 만들 수 있어야 한다',
(category: GroupCategory) => {
expect(() =>
authorizer.createGroup({
authorizer.authorizeForCreateGroup({
user,
category,
grade: GroupAccessGrade.ALL,
Expand All @@ -56,7 +56,7 @@ describe('GroupAuthorizer', () => {
'공개 범위 %s의 그룹을 만들 수 있어야 한다',
(grade: GroupAccessGrade) => {
expect(() =>
authorizer.createGroup({
authorizer.authorizeForCreateGroup({
user,
category: GroupCategory.STUDY,
grade,
Expand All @@ -75,7 +75,7 @@ describe('GroupAuthorizer', () => {
'%s 유형의 그룹을 만들 때 예외가 발생해야 한다',
(category: GroupCategory) => {
expect(() =>
authorizer.createGroup({
authorizer.authorizeForCreateGroup({
user,
category,
grade: GroupAccessGrade.ALL,
Expand All @@ -88,7 +88,7 @@ describe('GroupAuthorizer', () => {
'공개 범위 %s의 그룹을 만들 때 예외가 발생해야 한다',
(grade: GroupAccessGrade) => {
expect(() =>
authorizer.createGroup({
authorizer.authorizeForCreateGroup({
user,
category: GroupCategory.STUDY,
grade,
Expand All @@ -98,4 +98,72 @@ describe('GroupAuthorizer', () => {
);
});
});

describe('authorizeForModifyGroup', () => {
let user: User;

describe('운영진 유저라면', () => {
beforeEach(() => {
user = DomainFixture.generateUser({ manager: true });
});

test.each(Object.values(GroupCategory))(
'그룹을 %s 유형으로 수정할 수 있어야 한다',
(category: GroupCategory) => {
expect(() =>
authorizer.authorizeForModifyGroup({
user,
nextCategory: category,
nextGrade: GroupAccessGrade.ALL,
}),
).not.toThrow();
},
);

test.each(Object.values(GroupAccessGrade))(
'그룹을 %s 공개 범위로 수정할 수 있어야 한다',
(grade: GroupAccessGrade) => {
expect(() =>
authorizer.authorizeForModifyGroup({
user,
nextCategory: GroupCategory.STUDY,
nextGrade: grade,
}),
).not.toThrow();
},
);
});

describe('일반 유저라면', () => {
beforeEach(() => {
user = DomainFixture.generateUser({ manager: false });
});

test.each(Object.values(ManagerOnlyGroupCategory))(
'그룹을 %s 유형으로 수정할 때 예외가 발생해야 한다',
(category: GroupCategory) => {
expect(() =>
authorizer.authorizeForModifyGroup({
user,
nextCategory: category,
nextGrade: GroupAccessGrade.ALL,
}),
).toThrowError(Message.CANNOT_MODIFY_GROUP);
},
);

test.each(Object.values(ManagerOnlyGroupAccessGrade))(
'그룹을 %s 공개 범위로 수정할 때 예외가 발생해야 한다',
(grade: GroupAccessGrade) => {
expect(() =>
authorizer.authorizeForModifyGroup({
user,
nextCategory: GroupCategory.STUDY,
nextGrade: grade,
}),
).toThrowError(Message.CANNOT_MODIFY_GROUP);
},
);
});
});
});
27 changes: 26 additions & 1 deletion src/app/application/group/authorizer/GroupAuthorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ type CreateGroupParams = {
grade: GroupAccessGrade;
};

type ModifyGroupParams = {
user: User;
nextCategory: GroupCategory | null;
nextGrade: GroupAccessGrade | null;
};

@Injectable()
export class GroupAuthorizer {
createGroup(params: CreateGroupParams): void {
authorizeForCreateGroup(params: CreateGroupParams): void {
const { user, category, grade } = params;

if (user.manager) {
Expand All @@ -32,4 +38,23 @@ export class GroupAuthorizer {
throw new ForbiddenException(Message.CANNOT_CREATE_GROUP);
}
}

authorizeForModifyGroup(params: ModifyGroupParams): void {
const { user, nextCategory, nextGrade } = params;

if (user.manager) {
return;
}

if (!nextCategory || !nextGrade) {
return;
}

if (
ManagerOnlyGroupCategory.includes(nextCategory) ||
ManagerOnlyGroupAccessGrade.includes(nextGrade)
) {
throw new ForbiddenException(Message.CANNOT_MODIFY_GROUP);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Test } from '@nestjs/testing';
import { advanceTo, clear } from 'jest-date-mock';

import { CreateGroupCommand } from '@sight/app/application/group/command/createGroup/CreateGroupCommand';
import { CreateGroupCommandHandler } from '@sight/app/application/group/command/createGroup/CreateGroupCommandHandler';

import { GroupFactory } from '@sight/app/domain/group/GroupFactory';
import { GroupMemberFactory } from '@sight/app/domain/group/GroupMemberFactory';
import { GroupInterestFactory } from '@sight/app/domain/interest/GroupInterestFactory';
import { Group } from '@sight/app/domain/group/model/Group';
import { GroupMember } from '@sight/app/domain/group/model/GroupMember';
import {
GroupMemberRepository,
IGroupMemberRepository,
Expand All @@ -15,34 +17,24 @@ import {
IGroupRepository,
} from '@sight/app/domain/group/IGroupRepository';
import {
GroupInterestRepository,
IGroupInterestRepository,
} from '@sight/app/domain/interest/IGroupInterestRepository';
GroupAccessGrade,
GroupCategory,
} from '@sight/app/domain/group/model/constant';
import {
IInterestRepository,
InterestRepository,
} from '@sight/app/domain/interest/IInterestRepository';

import { generateEmptyProviders } from '@sight/__test__/util';
import { CreateGroupCommand } from './CreateGroupCommand';
import {
GroupAccessGrade,
GroupCategory,
} from '@sight/app/domain/group/model/constant';
import { DomainFixture } from '@sight/__test__/fixtures';
import { generateEmptyProviders } from '@sight/__test__/util';
import { Message } from '@sight/constant/message';
import { Group } from '@sight/app/domain/group/model/Group';
import { GroupMember } from '@sight/app/domain/group/model/GroupMember';
import { GroupInterest } from '@sight/app/domain/interest/model/GroupInterest';

describe('CreateGroupCommandHandler', () => {
let handler: CreateGroupCommandHandler;
let groupFactory: jest.Mocked<GroupFactory>;
let groupMemberFactory: jest.Mocked<GroupMemberFactory>;
let groupInterestFactory: jest.Mocked<GroupInterestFactory>;
let groupRepository: jest.Mocked<IGroupRepository>;
let groupMemberRepository: jest.Mocked<IGroupMemberRepository>;
let groupInterestRepository: jest.Mocked<IGroupInterestRepository>;
let interestRepository: jest.Mocked<IInterestRepository>;

beforeAll(async () => {
Expand All @@ -54,10 +46,8 @@ describe('CreateGroupCommandHandler', () => {
...generateEmptyProviders(
GroupFactory,
GroupMemberFactory,
GroupInterestFactory,
GroupRepository,
GroupMemberRepository,
GroupInterestRepository,
InterestRepository,
),
],
Expand All @@ -66,10 +56,8 @@ describe('CreateGroupCommandHandler', () => {
handler = testModule.get(CreateGroupCommandHandler);
groupFactory = testModule.get(GroupFactory);
groupMemberFactory = testModule.get(GroupMemberFactory);
groupInterestFactory = testModule.get(GroupInterestFactory);
groupRepository = testModule.get(GroupRepository);
groupMemberRepository = testModule.get(GroupMemberRepository);
groupInterestRepository = testModule.get(GroupInterestRepository);
interestRepository = testModule.get(InterestRepository);
});

Expand All @@ -81,7 +69,6 @@ describe('CreateGroupCommandHandler', () => {
let command: CreateGroupCommand;
let newGroup: Group;
let groupMember: GroupMember;
let groupInterest: GroupInterest;

const userId = 'userId';
const title = 'title';
Expand Down Expand Up @@ -110,9 +97,6 @@ describe('CreateGroupCommandHandler', () => {
groupId: newGroup.id,
userId: userId,
});
groupInterest = DomainFixture.generateGroupInterest({
groupId: newGroup.id,
});

const interests = interestIds.map((interestId) =>
DomainFixture.generateInterest({ id: interestId }),
Expand All @@ -123,14 +107,9 @@ describe('CreateGroupCommandHandler', () => {
groupRepository.nextId = jest.fn().mockReturnValue(newGroup.id);
groupMemberFactory.create = jest.fn().mockReturnValue(groupMember);
groupMemberRepository.nextId = jest.fn().mockReturnValue(groupMember.id);
groupInterestFactory.create = jest.fn().mockReturnValue(groupInterest);
groupInterestRepository.nextId = jest
.fn()
.mockReturnValue(groupInterest.id);

groupRepository.save = jest.fn();
groupMemberRepository.save = jest.fn();
groupInterestRepository.save = jest.fn();
});

test('존재하지 않는 관심사일 때 예외가 발생해야 한다', async () => {
Expand Down Expand Up @@ -158,37 +137,5 @@ describe('CreateGroupCommandHandler', () => {
expect(groupMemberRepository.save).toBeCalledTimes(1);
expect(groupMemberRepository.save).toBeCalledWith(groupMember);
});

test('새로운 그룹 관심사 정보를 중복되지 않게 생성해야 한다', async () => {
const duplicatedInterestIds = ['some', 'some', 'other'];
const uniqueInterestIds = Array.from(new Set(duplicatedInterestIds));

command = {
...command,
interestIds: duplicatedInterestIds,
};
interestRepository.findByIds = jest
.fn()
.mockResolvedValue(
uniqueInterestIds.map((interestId) =>
DomainFixture.generateInterest({ id: interestId }),
),
);

await handler.execute(command);

expect(groupInterestFactory.create).toBeCalledTimes(
uniqueInterestIds.length,
);
});

test('새로운 그룹 관심사 정보를 생성한 뒤 저장해야 한다', async () => {
await handler.execute(command);

expect(groupInterestFactory.create).toBeCalledTimes(1);

expect(groupInterestRepository.save).toBeCalledTimes(1);
expect(groupInterestRepository.save).toBeCalledWith(groupInterest);
});
});
});
Loading

0 comments on commit 5323f7a

Please sign in to comment.