Skip to content

Commit

Permalink
BC-5833-Add Submissions Entities Deletion to the Main User Deletion U…
Browse files Browse the repository at this point in the history
…se Case (#4719)
  • Loading branch information
sszafGCA authored Feb 15, 2024
1 parent 3c33c48 commit e26897b
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 13 deletions.
40 changes: 38 additions & 2 deletions apps/server/src/modules/deletion/uc/deletion-request.uc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ObjectId } from 'bson';
import { RegistrationPinService } from '@modules/registration-pin';
import { FilesStorageClientAdapterService } from '@src/modules/files-storage-client';
import { DomainName, OperationType } from '@shared/domain/types';
import { TaskService } from '@modules/task';
import { SubmissionService, TaskService } from '@modules/task';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { NewsService } from '@src/modules/news/service/news.service';
import { DeletionStatusModel } from '../domain/types';
Expand Down Expand Up @@ -49,6 +49,7 @@ describe(DeletionRequestUc.name, () => {
let dashboardService: DeepMocked<DashboardService>;
let taskService: DeepMocked<TaskService>;
let newsService: DeepMocked<NewsService>;
let submissionService: DeepMocked<SubmissionService>;

beforeAll(async () => {
module = await Test.createTestingModule({
Expand Down Expand Up @@ -127,6 +128,7 @@ describe(DeletionRequestUc.name, () => {
useValue: createMock<TaskService>(),
},
{ provide: NewsService, useValue: createMock<NewsService>() },
{ provide: SubmissionService, useValue: createMock<SubmissionService>() },
],
}).compile();

Expand All @@ -149,6 +151,7 @@ describe(DeletionRequestUc.name, () => {
dashboardService = module.get(DashboardService);
taskService = module.get(TaskService);
newsService = module.get(NewsService);
submissionService = module.get(SubmissionService);
await setupEntities();
});

Expand Down Expand Up @@ -295,6 +298,13 @@ describe(DeletionRequestUc.name, () => {
new ObjectId().toHexString(),
]);

const submissionsDeleted = DomainOperationBuilder.build(DomainName.SUBMISSIONS, OperationType.DELETE, 1, [
new ObjectId().toHexString(),
]);
const submissionsUpdated = DomainOperationBuilder.build(DomainName.SUBMISSIONS, OperationType.UPDATE, 1, [
new ObjectId().toHexString(),
]);

const user = userDoFactory.buildWithId();

accountService.deleteAccountByUserId.mockResolvedValueOnce(accountDeleted);
Expand All @@ -316,6 +326,8 @@ describe(DeletionRequestUc.name, () => {
taskService.removeUserFromFinished.mockResolvedValueOnce(tasksModifiedByRemoveUserFromFinished);
taskService.deleteTasksByOnlyCreator.mockResolvedValueOnce(tasksDeleted);
newsService.deleteCreatorOrUpdaterReference.mockResolvedValueOnce(newsUpdated);
submissionService.deleteSingleSubmissionsOwnedByUser.mockResolvedValueOnce(submissionsDeleted);
submissionService.removeUserReferencesFromSubmissions.mockResolvedValueOnce(submissionsUpdated);

return {
deletionRequestToExecute,
Expand Down Expand Up @@ -553,14 +565,38 @@ describe(DeletionRequestUc.name, () => {
expect(newsService.deleteCreatorOrUpdaterReference).toHaveBeenCalledWith(deletionRequestToExecute.targetRefId);
});

it('should call submissionService.deleteSubmissionsByUserId to delete submissions', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(submissionService.deleteSingleSubmissionsOwnedByUser).toHaveBeenCalledWith(
deletionRequestToExecute.targetRefId
);
});

it('should call submissionService.updateSubmissionByUserId to update submissions', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(submissionService.removeUserReferencesFromSubmissions).toHaveBeenCalledWith(
deletionRequestToExecute.targetRefId
);
});

it('should call deletionLogService.createDeletionLog to create logs for deletionRequest', async () => {
const { deletionRequestToExecute } = setup();

deletionRequestService.findAllItemsToExecute.mockResolvedValueOnce([deletionRequestToExecute]);

await uc.executeDeletionRequests();

expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(13);
expect(deletionLogService.createDeletionLog).toHaveBeenCalledTimes(15);
});
});

Expand Down
29 changes: 28 additions & 1 deletion apps/server/src/modules/deletion/uc/deletion-request.uc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Injectable } from '@nestjs/common';
import { DomainName, EntityId, OperationType } from '@shared/domain/types';
import { LegacyLogger } from '@src/core/logger';
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { TaskService } from '@modules/task';
import { SubmissionService, TaskService } from '@modules/task';
import { DomainOperation } from '@shared/domain/interface';
import { DomainOperationBuilder } from '@shared/domain/builder/domain-operation.builder';
import { NewsService } from '@src/modules/news/service/news.service';
Expand Down Expand Up @@ -43,6 +43,7 @@ export class DeletionRequestUc {
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService,
private readonly dashboardService: DashboardService,
private readonly taskService: TaskService,
private readonly submissionService: SubmissionService,
private readonly newsService: NewsService
) {
this.logger.setContext(DeletionRequestUc.name);
Expand Down Expand Up @@ -113,6 +114,7 @@ export class DeletionRequestUc {
this.removeUserFromRocketChat(deletionRequest),
this.removeUsersDashboard(deletionRequest),
this.removeUserFromTasks(deletionRequest),
this.removeUserFromSubmissions(deletionRequest),
this.removeUsersDataFromNews(deletionRequest),
]);
await this.deletionRequestService.markDeletionRequestAsExecuted(deletionRequest.id);
Expand Down Expand Up @@ -374,6 +376,31 @@ export class DeletionRequestUc {
);
}

private async removeUserFromSubmissions(deletionRequest: DeletionRequest) {
this.logger.debug({ action: 'removeUserFromSubmissions', deletionRequest });

const [submissionsDeleted, submissionsModified] = await Promise.all([
this.submissionService.deleteSingleSubmissionsOwnedByUser(deletionRequest.targetRefId),
this.submissionService.removeUserReferencesFromSubmissions(deletionRequest.targetRefId),
]);

await this.logDeletion(
deletionRequest,
submissionsDeleted.domain,
submissionsDeleted.operation,
submissionsDeleted.count,
submissionsDeleted.refs
);

await this.logDeletion(
deletionRequest,
submissionsModified.domain,
submissionsModified.operation,
submissionsModified.count,
submissionsModified.refs
);
}

private async removeUsersDataFromNews(deletionRequest: DeletionRequest) {
this.logger.debug({ action: 'removeUsersDataFromNews', deletionRequest });
const newsModified = await this.newsService.deleteCreatorOrUpdaterReference(deletionRequest.targetRefId);
Expand Down
108 changes: 107 additions & 1 deletion apps/server/src/modules/task/service/submission.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { Test, TestingModule } from '@nestjs/testing';
import { Submission } from '@shared/domain/entity';
import { Counted } from '@shared/domain/types';
import { SubmissionRepo } from '@shared/repo';
import { setupEntities, submissionFactory, taskFactory } from '@shared/testing';
import { setupEntities, submissionFactory, taskFactory, userFactory } from '@shared/testing';
import { Logger } from '@src/core/logger';
import { ObjectId } from 'bson';
import { SubmissionService } from './submission.service';

describe('Submission Service', () => {
Expand All @@ -29,6 +31,10 @@ describe('Submission Service', () => {
provide: FilesStorageClientAdapterService,
useValue: createMock<FilesStorageClientAdapterService>(),
},
{
provide: Logger,
useValue: createMock<Logger>(),
},
],
}).compile();

Expand Down Expand Up @@ -211,4 +217,104 @@ describe('Submission Service', () => {
});
});
});

describe('deleteSingleSubmissionsOwnedByUser', () => {
describe('when submission with specified userId was not found ', () => {
const setup = () => {
const submission = submissionFactory.buildWithId();

submissionRepo.findAllByUserId.mockResolvedValueOnce([[], 0]);

return { submission };
};

it('should return deletedSubmissions number of 0', async () => {
const { submission } = setup();

const result = await service.deleteSingleSubmissionsOwnedByUser(new ObjectId().toString());

expect(result.count).toEqual(0);
expect(result.refs.length).toEqual(0);
expect(submission).toBeDefined();
});
});

describe('when submission with specified userId was found ', () => {
const setup = () => {
const user = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({ student: user, teamMembers: [user] });

submissionRepo.findAllByUserId.mockResolvedValueOnce([[submission], 1]);
submissionRepo.delete.mockResolvedValueOnce();

return { submission, user };
};

it('should return deletedSubmissions number of 1', async () => {
const { submission, user } = setup();

const result = await service.deleteSingleSubmissionsOwnedByUser(user.id);

expect(result.count).toEqual(1);
expect(result.refs.length).toEqual(1);
expect(submissionRepo.delete).toBeCalledTimes(1);
expect(submissionRepo.delete).toHaveBeenCalledWith([submission]);
});
});
});

describe('removeUserReferencesFromSubmissions', () => {
describe('when submission with specified userId was not found ', () => {
const setup = () => {
const user1 = userFactory.buildWithId();
const user2 = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({ student: user1, teamMembers: [user1, user2] });

submissionRepo.findAllByUserId.mockResolvedValueOnce([[], 0]);

return { submission, user1, user2 };
};

it('should return updated submission number of 0', async () => {
const { submission, user1 } = setup();

const result = await service.removeUserReferencesFromSubmissions(new ObjectId().toString());

expect(result.count).toEqual(0);
expect(result.refs.length).toEqual(0);
expect(submission.student).toEqual(user1);
expect(submission.teamMembers.length).toEqual(2);
});
});

describe('when submission with specified userId was found ', () => {
const setup = () => {
const user1 = userFactory.buildWithId();
const user2 = userFactory.buildWithId();
const submission = submissionFactory.buildWithId({
student: user1,
teamMembers: [user1, user2],
});

submissionRepo.findAllByUserId.mockResolvedValueOnce([[submission], 1]);
submissionRepo.delete.mockResolvedValueOnce();

return { submission, user1, user2 };
};

it('should return updated submission number of 1', async () => {
const { submission, user1, user2 } = setup();

const result = await service.removeUserReferencesFromSubmissions(user1.id);

expect(result.count).toEqual(1);
expect(result.refs.length).toEqual(1);
expect(submission.student).toBeUndefined();
expect(submission.teamMembers.length).toEqual(1);
expect(submission.teamMembers[0]).toEqual(user2);
expect(submissionRepo.save).toBeCalledTimes(1);
expect(submissionRepo.save).toHaveBeenCalledWith([submission]);
});
});
});
});
99 changes: 97 additions & 2 deletions apps/server/src/modules/task/service/submission.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { FilesStorageClientAdapterService } from '@modules/files-storage-client';
import { Injectable } from '@nestjs/common';
import { DataDeletionDomainOperationLoggable } from '@shared/common/loggable';
import { DomainOperationBuilder } from '@shared/domain/builder';
import { Submission } from '@shared/domain/entity';
import { Counted, EntityId } from '@shared/domain/types';
import { DomainOperation } from '@shared/domain/interface';
import { Counted, DomainName, EntityId, OperationType, StatusModel } from '@shared/domain/types';
import { SubmissionRepo } from '@shared/repo';
import { Logger } from '@src/core/logger';

@Injectable()
export class SubmissionService {
constructor(
private readonly submissionRepo: SubmissionRepo,
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService
private readonly filesStorageClientAdapterService: FilesStorageClientAdapterService,
private readonly logger: Logger
) {}

async findById(submissionId: EntityId): Promise<Submission> {
Expand All @@ -26,4 +31,94 @@ export class SubmissionService {

await this.submissionRepo.delete(submission);
}

async deleteSingleSubmissionsOwnedByUser(userId: EntityId): Promise<DomainOperation> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting single Submissions owned by user',
DomainName.SUBMISSIONS,
userId,
StatusModel.PENDING
)
);
let [submissionsEntities, submissionsCount] = await this.submissionRepo.findAllByUserId(userId);

if (submissionsCount > 0) {
submissionsEntities = submissionsEntities.filter((submission) => submission.isSingleSubmissionOwnedByUser());
submissionsCount = submissionsEntities.length;
}

if (submissionsCount > 0) {
await this.submissionRepo.delete(submissionsEntities);
}

const result = DomainOperationBuilder.build(
DomainName.SUBMISSIONS,
OperationType.DELETE,
submissionsCount,
this.getSubmissionsId(submissionsEntities)
);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully deleted single Submissions owned by user',
DomainName.SUBMISSIONS,
userId,
StatusModel.FINISHED,
submissionsCount,
0
)
);

return result;
}

async removeUserReferencesFromSubmissions(userId: EntityId): Promise<DomainOperation> {
this.logger.info(
new DataDeletionDomainOperationLoggable(
'Deleting user references from Submissions',
DomainName.SUBMISSIONS,
userId,
StatusModel.PENDING
)
);

let [submissionsEntities, submissionsCount] = await this.submissionRepo.findAllByUserId(userId);

if (submissionsCount > 0) {
submissionsEntities = submissionsEntities.filter((submission) => submission.isGroupSubmission());
submissionsCount = submissionsEntities.length;
}

if (submissionsCount > 0) {
submissionsEntities.forEach((submission) => {
submission.removeStudentById(userId);
submission.removeUserFromTeamMembers(userId);
});

await this.submissionRepo.save(submissionsEntities);
}
const result = DomainOperationBuilder.build(
DomainName.SUBMISSIONS,
OperationType.UPDATE,
submissionsCount,
this.getSubmissionsId(submissionsEntities)
);

this.logger.info(
new DataDeletionDomainOperationLoggable(
'Successfully deleted references from Submissions collection',
DomainName.SUBMISSIONS,
userId,
StatusModel.FINISHED,
submissionsCount,
0
)
);
return result;
}

private getSubmissionsId(submissions: Submission[]): EntityId[] {
return submissions.map((submission) => submission.id);
}
}
Loading

0 comments on commit e26897b

Please sign in to comment.