feat: As a user I want to edit my comment
This commit is contained in:
parent
6f209dfbb4
commit
f5e34722f5
|
@ -54,5 +54,4 @@
|
||||||
</table>
|
</table>
|
||||||
</nb-card>
|
</nb-card>
|
||||||
|
|
||||||
<!--ToDo: Add loading spinner after routing fix to avoid circular dependency issues -->
|
|
||||||
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
<app-loading-spinner [isLoading$]="isLoading()" *ngIf="isLoading() | async"></app-loading-spinner>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {catchError, filter, mergeMap, switchMap, tap} from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
Comment,
|
Comment,
|
||||||
CommentDialogBody,
|
CommentDialogBody,
|
||||||
CommentEntry,
|
CommentEntry, RelatedFindingOption,
|
||||||
transformCommentsToObjectiveEntries,
|
transformCommentsToObjectiveEntries,
|
||||||
transformCommentToRequestBody
|
transformCommentToRequestBody
|
||||||
} from '@shared/models/comment.model';
|
} from '@shared/models/comment.model';
|
||||||
|
@ -17,11 +17,13 @@ import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
import {ProjectState} from '@shared/stores/project-state/project-state';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {PentestStatus} from '@shared/models/pentest-status.model';
|
import {PentestStatus} from '@shared/models/pentest-status.model';
|
||||||
import {FindingDialogComponent} from '@shared/modules/finding-dialog/finding-dialog.component';
|
|
||||||
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
import {DialogService} from '@shared/services/dialog-service/dialog.service';
|
||||||
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
|
import {CommentDialogService} from '@shared/modules/comment-dialog/service/comment-dialog.service';
|
||||||
import {CommentService} from '@shared/services/comment.service';
|
import {CommentService} from '@shared/services/comment.service';
|
||||||
import {UpdatePentestComments, UpdatePentestFindings} from '@shared/stores/project-state/project-state.actions';
|
import {UpdatePentestComments} from '@shared/stores/project-state/project-state.actions';
|
||||||
|
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
||||||
|
import {Finding} from '@shared/models/finding.model';
|
||||||
|
import {FindingService} from '@shared/services/finding.service';
|
||||||
|
|
||||||
@UntilDestroy()
|
@UntilDestroy()
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -36,6 +38,7 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
notStartedStatus: PentestStatus = PentestStatus.NOT_STARTED;
|
||||||
|
|
||||||
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
pentestInfo$: BehaviorSubject<Pentest> = new BehaviorSubject<Pentest>(null);
|
||||||
|
objectiveFindings: RelatedFindingOption[] = [];
|
||||||
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
// comments$: BehaviorSubject<Comment[]> = new BehaviorSubject<Comment[]>(null);
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
|
@ -43,7 +46,6 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
|
CommentColumns.COMMENT_ID, CommentColumns.TITLE, CommentColumns.DESCRIPTION, CommentColumns.RELATED_FINDINGS, CommentColumns.ACTIONS
|
||||||
];
|
];
|
||||||
dataSource: NbTreeGridDataSource<CommentEntry>;
|
dataSource: NbTreeGridDataSource<CommentEntry>;
|
||||||
|
|
||||||
data: CommentEntry[] = [];
|
data: CommentEntry[] = [];
|
||||||
|
|
||||||
getters: NbGetters<CommentEntry, CommentEntry> = {
|
getters: NbGetters<CommentEntry, CommentEntry> = {
|
||||||
|
@ -53,6 +55,7 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private readonly commentService: CommentService,
|
constructor(private readonly commentService: CommentService,
|
||||||
|
private readonly findingService: FindingService,
|
||||||
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
private dataSourceBuilder: NbTreeGridDataSourceBuilder<CommentEntry>,
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService,
|
||||||
|
@ -68,6 +71,7 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
next: (selectedPentest: Pentest) => {
|
next: (selectedPentest: Pentest) => {
|
||||||
this.pentestInfo$.next(selectedPentest);
|
this.pentestInfo$.next(selectedPentest);
|
||||||
this.loadCommentsData();
|
this.loadCommentsData();
|
||||||
|
this.requestFindingsData(selectedPentest.id);
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -103,8 +107,9 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
|
|
||||||
onClickAddComment(): void {
|
onClickAddComment(): void {
|
||||||
this.commentDialogService.openCommentDialog(
|
this.commentDialogService.openCommentDialog(
|
||||||
FindingDialogComponent,
|
CommentDialogComponent,
|
||||||
this.pentestInfo$.getValue().findingIds,
|
this.pentestInfo$.getValue().findingIds,
|
||||||
|
this.objectiveFindings,
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
@ -114,7 +119,6 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(value => !!value),
|
filter(value => !!value),
|
||||||
tap((value) => console.warn('CommentDialogBody: ', value)),
|
|
||||||
mergeMap((value: CommentDialogBody) =>
|
mergeMap((value: CommentDialogBody) =>
|
||||||
this.commentService.saveComment(
|
this.commentService.saveComment(
|
||||||
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
this.pentestInfo$.getValue() ? this.pentestInfo$.getValue().id : '',
|
||||||
|
@ -126,19 +130,74 @@ export class PentestCommentsComponent implements OnInit {
|
||||||
next: (newComment: Comment) => {
|
next: (newComment: Comment) => {
|
||||||
this.store.dispatch(new UpdatePentestComments(newComment.id));
|
this.store.dispatch(new UpdatePentestComments(newComment.id));
|
||||||
this.loadCommentsData();
|
this.loadCommentsData();
|
||||||
// Todo: Fix trans keys
|
|
||||||
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
|
this.notificationService.showPopup('comment.popup.save.success', PopupType.SUCCESS);
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
// Todo: Fix trans keys
|
|
||||||
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
|
this.notificationService.showPopup('comment.popup.save.failed', PopupType.FAILURE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickEditComment(commentEntry): void {
|
onClickEditComment(commentEntry): void {
|
||||||
console.info('Coming soon..', commentEntry);
|
this.commentService.getCommentById(commentEntry.data.commentId).pipe(
|
||||||
|
filter(isNotNullOrUndefined),
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (existingComment: Comment) => {
|
||||||
|
if (existingComment) {
|
||||||
|
this.commentDialogService.openCommentDialog(
|
||||||
|
CommentDialogComponent,
|
||||||
|
this.pentestInfo$.getValue().findingIds,
|
||||||
|
this.objectiveFindings,
|
||||||
|
existingComment,
|
||||||
|
{
|
||||||
|
closeOnEsc: false,
|
||||||
|
hasScroll: false,
|
||||||
|
autoFocus: false,
|
||||||
|
closeOnBackdropClick: false
|
||||||
|
}
|
||||||
|
).pipe(
|
||||||
|
filter(value => !!value),
|
||||||
|
mergeMap((value: CommentDialogBody) =>
|
||||||
|
this.commentService.updateComment(
|
||||||
|
commentEntry.data.commentId,
|
||||||
|
transformCommentToRequestBody(value)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (updatedComment: Comment) => {
|
||||||
|
this.loadCommentsData();
|
||||||
|
this.notificationService.showPopup('comment.popup.update.success', PopupType.SUCCESS);
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
this.notificationService.showPopup('comment.popup.update.failed', PopupType.FAILURE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.notificationService.showPopup('comment.popup.not.available', PopupType.INFO);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
requestFindingsData(pentestId: string): void {
|
||||||
|
this.objectiveFindings = [];
|
||||||
|
this.findingService.getFindingsByPentestId(pentestId).pipe(
|
||||||
|
untilDestroyed(this)
|
||||||
|
).subscribe({
|
||||||
|
next: (findings: Finding[]) => {
|
||||||
|
findings.forEach(finding => this.objectiveFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
|
||||||
|
},
|
||||||
|
error: err => {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickDeleteComment(commentEntry): void {
|
onClickDeleteComment(commentEntry): void {
|
||||||
|
|
|
@ -163,7 +163,6 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
untilDestroyed(this)
|
untilDestroyed(this)
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (updatedFinding: Finding) => {
|
next: (updatedFinding: Finding) => {
|
||||||
this.store.dispatch(new UpdatePentestFindings(updatedFinding.id));
|
|
||||||
this.loadFindingsData();
|
this.loadFindingsData();
|
||||||
this.notificationService.showPopup('finding.popup.update.success', PopupType.SUCCESS);
|
this.notificationService.showPopup('finding.popup.update.success', PopupType.SUCCESS);
|
||||||
},
|
},
|
||||||
|
@ -173,7 +172,7 @@ export class PentestFindingsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.notificationService.showPopup('finding.popup.not.available', PopupType.FAILURE);
|
this.notificationService.showPopup('finding.popup.not.available', PopupType.INFO);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: err => {
|
error: err => {
|
||||||
|
|
|
@ -181,7 +181,8 @@
|
||||||
"update.success": "Kommentar erfolgreich aktualisiert",
|
"update.success": "Kommentar erfolgreich aktualisiert",
|
||||||
"update.failed": "Kommentar konnte nicht aktualisiert werden",
|
"update.failed": "Kommentar konnte nicht aktualisiert werden",
|
||||||
"delete.success": "Kommentar erfolgreich gelöscht",
|
"delete.success": "Kommentar erfolgreich gelöscht",
|
||||||
"delete.failed": "Kommentar konnte nicht gelöscht werden"
|
"delete.failed": "Kommentar konnte nicht gelöscht werden",
|
||||||
|
"not.available": "Kommentar ist nicht mehr verfügbar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pentest": {
|
"pentest": {
|
||||||
|
|
|
@ -181,7 +181,8 @@
|
||||||
"update.success": "Comment updated successfully",
|
"update.success": "Comment updated successfully",
|
||||||
"update.failed": "Comment could not be updated",
|
"update.failed": "Comment could not be updated",
|
||||||
"delete.success": "Comment deleted successfully",
|
"delete.success": "Comment deleted successfully",
|
||||||
"delete.failed": "Comment could not be deleted"
|
"delete.failed": "Comment could not be deleted",
|
||||||
|
"not.available": "Comment is not available anymore"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pentest": {
|
"pentest": {
|
||||||
|
|
|
@ -49,7 +49,7 @@ export function transformCommentToRequestBody(comment: CommentDialogBody | Comme
|
||||||
title: comment.title,
|
title: comment.title,
|
||||||
description: comment.description,
|
description: comment.description,
|
||||||
// Transforms related findings from RelatedFindingOption to list of finding ids
|
// Transforms related findings from RelatedFindingOption to list of finding ids
|
||||||
relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.value.id) : [],
|
relatedFindings: comment.relatedFindings ? comment.relatedFindings.map(finding => finding.id) : [],
|
||||||
/* Remove Table Entry Object Properties */
|
/* Remove Table Entry Object Properties */
|
||||||
childEntries: undefined,
|
childEntries: undefined,
|
||||||
kind: undefined,
|
kind: undefined,
|
||||||
|
|
|
@ -23,4 +23,5 @@ export interface GenericFormFieldOption {
|
||||||
headerLabelKey: string;
|
headerLabelKey: string;
|
||||||
buttonKey: string;
|
buttonKey: string;
|
||||||
accentColor: string;
|
accentColor: string;
|
||||||
|
additionalData?: any;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,13 +57,16 @@
|
||||||
{{formArray[2].labelKey | translate}}
|
{{formArray[2].labelKey | translate}}
|
||||||
</label>
|
</label>
|
||||||
<!--<fa-icon nbPrefix [icon]="fa.faExclamationCircle" size="lg" class="finding-icon"></fa-icon>-->
|
<!--<fa-icon nbPrefix [icon]="fa.faExclamationCircle" size="lg" class="finding-icon"></fa-icon>-->
|
||||||
<nb-select placeholder="{{formArray[2].placeholder | translate}}" formControlName="{{formArray[2].fieldName}}" multiple
|
<nb-select placeholder="{{formArray[2].placeholder | translate}}"
|
||||||
|
id="{{formArray[2].fieldName}}"
|
||||||
|
formControlName="{{formArray[2].fieldName}}"
|
||||||
(selectedChange)="changeSelected($event)"
|
(selectedChange)="changeSelected($event)"
|
||||||
fullWidth shape="semi-round" filled status="info" [size]="'large'" class="form-field relatedFindings">
|
multiple fullWidth shape="semi-round" filled status="info"
|
||||||
<nb-option class="reset-option">{{'global.action.reset' | translate}}</nb-option>
|
[size]="'large'" class="form-field relatedFindings">
|
||||||
<nb-option class="finding-option" *ngFor="let finding of relatedFindings" [value]="{value: finding}">
|
<nb-option class="reset-option">{{'global.action.reset' | translate}}</nb-option>
|
||||||
{{finding.title}}
|
<nb-option class="finding-option" *ngFor="let finding of relatedFindings" [value]="finding">
|
||||||
</nb-option>
|
{{finding.title}}
|
||||||
|
</nb-option>
|
||||||
</nb-select>
|
</nb-select>
|
||||||
</nb-form-field>
|
</nb-form-field>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -182,7 +182,8 @@ export const mockedCommentDialogData = {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'comment.create.header',
|
headerLabelKey: 'comment.create.header',
|
||||||
buttonKey: 'global.action.save',
|
buttonKey: 'global.action.save',
|
||||||
accentColor: 'info'
|
accentColor: 'info',
|
||||||
|
additionalData: []
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,11 @@
|
||||||
import {ChangeDetectionStrategy, Component, Inject, OnChanges, OnInit} from '@angular/core';
|
import {ChangeDetectionStrategy, Component, Inject, OnInit} from '@angular/core';
|
||||||
import {FormBuilder, FormGroup} from '@angular/forms';
|
import {FormBuilder, FormGroup} from '@angular/forms';
|
||||||
import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
|
import {GenericDialogData, GenericFormFieldConfig} from '@shared/models/generic-dialog-data';
|
||||||
import * as FA from '@fortawesome/free-solid-svg-icons';
|
import * as FA from '@fortawesome/free-solid-svg-icons';
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
import {NB_DIALOG_CONFIG, NbDialogRef} from '@nebular/theme';
|
||||||
import {PentestService} from '@shared/services/pentest.service';
|
import {UntilDestroy} from '@ngneat/until-destroy';
|
||||||
import {ProjectState} from '@shared/stores/project-state/project-state';
|
|
||||||
import {UntilDestroy, untilDestroyed} from '@ngneat/until-destroy';
|
|
||||||
import {Pentest} from '@shared/models/pentest.model';
|
|
||||||
import {Store} from '@ngxs/store';
|
|
||||||
import {Finding} from '@shared/models/finding.model';
|
|
||||||
import {RelatedFindingOption} from '@shared/models/comment.model';
|
import {RelatedFindingOption} from '@shared/models/comment.model';
|
||||||
import {BehaviorSubject} from 'rxjs';
|
|
||||||
import {FindingService} from '@shared/services/finding.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-comment-dialog',
|
selector: 'app-comment-dialog',
|
||||||
|
@ -39,28 +32,14 @@ export class CommentDialogComponent implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
@Inject(NB_DIALOG_CONFIG) private data: GenericDialogData,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
protected dialogRef: NbDialogRef<CommentDialogComponent>,
|
protected dialogRef: NbDialogRef<CommentDialogComponent>
|
||||||
private readonly findingService: FindingService,
|
|
||||||
private store: Store
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.commentFormGroup = this.generateFormCreationFieldArray();
|
|
||||||
this.dialogData = this.data;
|
this.dialogData = this.data;
|
||||||
// Resets related Findings input fields when comment was found in dialog context
|
this.relatedFindings = this.dialogData.options[0].additionalData;
|
||||||
// tslint:disable-next-line:no-string-literal
|
this.commentFormGroup = this.generateFormCreationFieldArray();
|
||||||
// this.commentFormGroup.controls['commentRelatedFindings'].reset('');
|
|
||||||
}
|
|
||||||
|
|
||||||
changeSelected($event): void {
|
|
||||||
// Latest Value
|
|
||||||
console.info($event[$event.length - 1].value);
|
|
||||||
// use this element at the end
|
|
||||||
// tslint:disable-next-line:no-string-literal
|
|
||||||
console.warn(this.commentFormGroup.controls['commentRelatedFindings'].value);
|
|
||||||
// tslint:disable-next-line:no-string-literal
|
|
||||||
this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFormCreationFieldArray(): FormGroup {
|
generateFormCreationFieldArray(): FormGroup {
|
||||||
|
@ -70,52 +49,27 @@ export class CommentDialogComponent implements OnInit {
|
||||||
[currentValue?.fieldName]: currentValue?.controlsConfig
|
[currentValue?.fieldName]: currentValue?.controlsConfig
|
||||||
}), {});
|
}), {});
|
||||||
// tslint:disable-next-line:no-string-literal
|
// tslint:disable-next-line:no-string-literal
|
||||||
const relatedFindings = this.data.form['commentRelatedFindings'].controlsConfig[0].value;
|
const preSelectedRelatedFindings = this.data.form['commentRelatedFindings'].controlsConfig[0].value;
|
||||||
if (relatedFindings && relatedFindings.length > 0) {
|
if (preSelectedRelatedFindings && preSelectedRelatedFindings.length > 0) {
|
||||||
// ToDo: Select included findings here (selectedFindings / initialSelectedFindings)
|
this.relatedFindings.forEach(finding => {
|
||||||
console.warn('IF (EDIT)', relatedFindings);
|
if (preSelectedRelatedFindings.includes(finding)) {
|
||||||
this.renderRelatedFindings(relatedFindings);
|
this.initialSelectedFindings.push(finding);
|
||||||
} else {
|
this.selectedFindings.push(finding);
|
||||||
this.renderRelatedFindings([]);
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return this.fb.group(config);
|
return this.fb.group(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderRelatedFindings(relatedFindings: any): void {
|
changeSelected($event): void {
|
||||||
this.store.select(ProjectState.pentest).pipe(
|
// tslint:disable-next-line:no-string-literal
|
||||||
untilDestroyed(this)
|
this.selectedFindings = this.commentFormGroup.controls['commentRelatedFindings'].value;
|
||||||
).subscribe({
|
|
||||||
next: (selectedPentest: Pentest) => {
|
|
||||||
// console.warn(selectedPentest.findingIds);
|
|
||||||
this.requestRelatedFindingsData(selectedPentest.id, relatedFindings);
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
requestRelatedFindingsData(pentestId: string, relatedFindings: any): void {
|
|
||||||
this.findingService.getFindingsByPentestId(pentestId).pipe(
|
|
||||||
untilDestroyed(this)
|
|
||||||
).subscribe({
|
|
||||||
next: (findings: Finding[]) => {
|
|
||||||
findings.forEach(finding => this.relatedFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
|
|
||||||
// ToDo: Only add the findings that were included in
|
|
||||||
console.info('initialSelectedFindings OnINIT ', relatedFindings);
|
|
||||||
// findings.forEach(finding => this.initialSelectedFindings.push({id: finding.id, title: finding.title} as RelatedFindingOption));
|
|
||||||
},
|
|
||||||
error: err => {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickSave(value: any): void {
|
onClickSave(value: any): void {
|
||||||
this.dialogRef.close({
|
this.dialogRef.close({
|
||||||
title: value.commentTitle,
|
title: value.commentTitle,
|
||||||
description: value.commentDescription,
|
description: value.commentDescription,
|
||||||
// ToDo: Refactor this to only include the ids this.commentFormGroup.controls['commentRelatedFindings'].value
|
|
||||||
relatedFindings: this.selectedFindings ? this.selectedFindings : []
|
relatedFindings: this.selectedFindings ? this.selectedFindings : []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -141,11 +95,6 @@ export class CommentDialogComponent implements OnInit {
|
||||||
newCommentData[key] = '';
|
newCommentData[key] = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/* ToDo: Use for EDIT implementation
|
|
||||||
console.info('initialSelectedFindings: ', this.initialSelectedFindings);
|
|
||||||
console.info('selectedFindings: ', this.selectedFindings);
|
|
||||||
console.info('deepEqual related Findings: ', deepEqual(this.initialSelectedFindings, this.selectedFindings));
|
|
||||||
*/
|
|
||||||
const didChange = !deepEqual(oldCommentData, newCommentData) || !deepEqual(this.initialSelectedFindings, this.selectedFindings);
|
const didChange = !deepEqual(oldCommentData, newCommentData) || !deepEqual(this.initialSelectedFindings, this.selectedFindings);
|
||||||
return didChange;
|
return didChange;
|
||||||
}
|
}
|
||||||
|
@ -155,17 +104,16 @@ export class CommentDialogComponent implements OnInit {
|
||||||
* @return parsed findingData
|
* @return parsed findingData
|
||||||
*/
|
*/
|
||||||
private parseInitializedCommentDialogData(dialogData: GenericDialogData): any {
|
private parseInitializedCommentDialogData(dialogData: GenericDialogData): any {
|
||||||
const findingData = {};
|
const commentData = {};
|
||||||
Object.entries(dialogData.form).forEach(entry => {
|
Object.entries(dialogData.form).forEach(entry => {
|
||||||
const [key, value] = entry;
|
const [key, value] = entry;
|
||||||
// console.info(key);
|
commentData[key] = value.controlsConfig[0] ?
|
||||||
findingData[key] = value.controlsConfig[0] ?
|
|
||||||
(value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
|
(value.controlsConfig[0].value ? value.controlsConfig[0].value : value.controlsConfig[0]) : '';
|
||||||
// Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list
|
// Related Findings form field can be ignored since changes here will be recognised inside commentRelatedFindings of tag-list
|
||||||
if (key === 'commentRelatedFindings') {
|
if (key === 'commentRelatedFindings') {
|
||||||
findingData[key] = '';
|
commentData[key] = '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return findingData;
|
return commentData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {CommentDialogService} from '@shared/modules/comment-dialog/service/comme
|
||||||
import {ComponentType} from '@angular/cdk/overlay';
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
import {NbDialogConfig} from '@nebular/theme';
|
import {NbDialogConfig} from '@nebular/theme';
|
||||||
import {Observable, of} from 'rxjs';
|
import {Observable, of} from 'rxjs';
|
||||||
import {Comment} from '@shared/models/comment.model';
|
import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
|
||||||
|
|
||||||
export class CommentDialogServiceMock implements Required<CommentDialogService> {
|
export class CommentDialogServiceMock implements Required<CommentDialogService> {
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ export class CommentDialogServiceMock implements Required<CommentDialogService>
|
||||||
openCommentDialog(
|
openCommentDialog(
|
||||||
componentOrTemplateRef: ComponentType<any>,
|
componentOrTemplateRef: ComponentType<any>,
|
||||||
findingIds: [],
|
findingIds: [],
|
||||||
|
relatedFindings: RelatedFindingOption[],
|
||||||
comment: Comment | undefined,
|
comment: Comment | undefined,
|
||||||
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
config: Partial<NbDialogConfig<Partial<any> | string>> | undefined): Observable<any> {
|
||||||
return of(undefined);
|
return of(undefined);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Injectable } from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import {NbDialogConfig, NbDialogService} from '@nebular/theme';
|
import {NbDialogConfig, NbDialogService} from '@nebular/theme';
|
||||||
import {GenericDialogData} from '@shared/models/generic-dialog-data';
|
import {GenericDialogData} from '@shared/models/generic-dialog-data';
|
||||||
import {ComponentType} from '@angular/cdk/overlay';
|
import {ComponentType} from '@angular/cdk/overlay';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {Validators} from '@angular/forms';
|
import {Validators} from '@angular/forms';
|
||||||
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
import {CommentDialogComponent} from '@shared/modules/comment-dialog/comment-dialog.component';
|
||||||
import {Comment} from '@shared/models/comment.model';
|
import {Comment, RelatedFindingOption} from '@shared/models/comment.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommentDialogService {
|
export class CommentDialogService {
|
||||||
|
@ -30,10 +30,20 @@ export class CommentDialogService {
|
||||||
|
|
||||||
public openCommentDialog(componentOrTemplateRef: ComponentType<any>,
|
public openCommentDialog(componentOrTemplateRef: ComponentType<any>,
|
||||||
findingIds: string[],
|
findingIds: string[],
|
||||||
|
relatedFindings: RelatedFindingOption[],
|
||||||
comment?: Comment,
|
comment?: Comment,
|
||||||
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
config?: Partial<NbDialogConfig<Partial<any> | string>>): Observable<any> {
|
||||||
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
let dialogOptions: Partial<NbDialogConfig<Partial<any> | string>>;
|
||||||
let dialogData: GenericDialogData;
|
let dialogData: GenericDialogData;
|
||||||
|
// Preselect related findings
|
||||||
|
const selectedRelatedFindings: RelatedFindingOption[] = [];
|
||||||
|
if (comment && comment.relatedFindings.length > 0 && relatedFindings) {
|
||||||
|
relatedFindings.forEach(finding => {
|
||||||
|
if (comment.relatedFindings.includes(finding.id)) {
|
||||||
|
selectedRelatedFindings.push(finding);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
// Setup CommentDialogBody
|
// Setup CommentDialogBody
|
||||||
dialogData = {
|
dialogData = {
|
||||||
form: {
|
form: {
|
||||||
|
@ -69,7 +79,10 @@ export class CommentDialogService {
|
||||||
labelKey: 'comment.relatedFindings.label',
|
labelKey: 'comment.relatedFindings.label',
|
||||||
placeholder: findingIds.length === 0 ? 'comment.noFindingsInObjectivePlaceholder' : 'comment.relatedFindingsPlaceholder',
|
placeholder: findingIds.length === 0 ? 'comment.noFindingsInObjectivePlaceholder' : 'comment.relatedFindingsPlaceholder',
|
||||||
controlsConfig: [
|
controlsConfig: [
|
||||||
{value: comment ? comment.relatedFindings : [], disabled: findingIds.length === 0},
|
{
|
||||||
|
value: comment ? selectedRelatedFindings : [],
|
||||||
|
disabled: findingIds.length === 0
|
||||||
|
},
|
||||||
[]
|
[]
|
||||||
],
|
],
|
||||||
errors: [
|
errors: [
|
||||||
|
@ -84,7 +97,8 @@ export class CommentDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'comment.edit.header',
|
headerLabelKey: 'comment.edit.header',
|
||||||
buttonKey: 'global.action.update',
|
buttonKey: 'global.action.update',
|
||||||
accentColor: 'warning'
|
accentColor: 'warning',
|
||||||
|
additionalData: relatedFindings
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
|
@ -92,7 +106,8 @@ export class CommentDialogService {
|
||||||
{
|
{
|
||||||
headerLabelKey: 'comment.create.header',
|
headerLabelKey: 'comment.create.header',
|
||||||
buttonKey: 'global.action.save',
|
buttonKey: 'global.action.save',
|
||||||
accentColor: 'info'
|
accentColor: 'info',
|
||||||
|
additionalData: relatedFindings
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {HttpClient} from '@angular/common/http';
|
||||||
import {Store} from '@ngxs/store';
|
import {Store} from '@ngxs/store';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {Comment} from '@shared/models/comment.model';
|
import {Comment} from '@shared/models/comment.model';
|
||||||
|
import {Finding} from '@shared/models/finding.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -26,6 +27,14 @@ export class CommentService {
|
||||||
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
return this.http.get<Comment[]>(`${this.apiBaseURL}/${pentestId}/comments`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Comment by Id
|
||||||
|
* @param commentId the id of the comment
|
||||||
|
*/
|
||||||
|
public getCommentById(commentId: string): Observable<Comment> {
|
||||||
|
return this.http.get<Comment>(`${this.apiBaseURL}/${commentId}/comment`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save Comment
|
* Save Comment
|
||||||
* @param pentestId the id of the pentest
|
* @param pentestId the id of the pentest
|
||||||
|
@ -35,6 +44,15 @@ export class CommentService {
|
||||||
return this.http.post<Comment>(`${this.apiBaseURL}/${pentestId}/comment`, comment);
|
return this.http.post<Comment>(`${this.apiBaseURL}/${pentestId}/comment`, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Comment
|
||||||
|
* @param commentId the id of the comment
|
||||||
|
* @param comment the information of the comment
|
||||||
|
*/
|
||||||
|
public updateComment(commentId: string, comment: Comment): Observable<Comment> {
|
||||||
|
return this.http.patch<Comment>(`${this.apiBaseURL}/${commentId}/comment`, comment);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Comment
|
* Delete Comment
|
||||||
* @param pentestId the id of the pentest
|
* @param pentestId the id of the pentest
|
||||||
|
|
|
@ -481,7 +481,7 @@
|
||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"affectedUrls\": []\n}",
|
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Test Comment Description\",\n \"relatedFindings\": []\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
|
@ -512,7 +512,7 @@
|
||||||
"bearer": [
|
"bearer": [
|
||||||
{
|
{
|
||||||
"key": "token",
|
"key": "token",
|
||||||
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzE3MTM3MzQsImlhdCI6MTY3MTcxMzQzNCwiYXV0aF90aW1lIjoxNjcxNzEyNjkwLCJqdGkiOiJjNWYxYWZiZi1mZTczLTQ0NTAtYjA4YS1lMGEwMDcyNjMyOTgiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODgvYXV0aC9yZWFsbXMvYzRwb19yZWFsbV9sb2NhbCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIxMGUwNmQ3YS04ZGQwLTRlY2QtODk2My0wNTZiNDUwNzljNGYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjNHBvX2xvY2FsIiwibm9uY2UiOiIyZTYyNjNhNC1lM2U2LTRlMzUtYjQ5Yy1lMjYyNzM1ZTk2MGQiLCJzZXNzaW9uX3N0YXRlIjoiMGNmYmY4MGEtNzAxMS00NmQzLTllNGQtNTUxYWU4NTA5NjZmIiwiYWNyIjoiMCIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJjNHBvX3VzZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYzRwb19sb2NhbCI6eyJyb2xlcyI6WyJ1c2VyIl19LCJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCB1c2VyIiwicHJlZmVycmVkX3VzZXJuYW1lIjoidHR0IiwiZ2l2ZW5fbmFtZSI6InRlc3QiLCJmYW1pbHlfbmFtZSI6InVzZXIifQ.se43hq_vPjzAG6MpIxBiHb9vJHZmbLEko0tiN5m2hbhzd8s3YiBWpeiI6kgZ5kzl23iBQyMnXN4Sqpbt2ERKbKyUusezWcXhGTP22usi3b1vzFOAY9mqCI32i15sxCM2UDRYDFYcAblaKPxKsQf6EWduXpcn4L9_kQE4EpoLyWWWqFThGvFPSvkPGodffcEOz8BrnYDVUnwkodFsOWAnQmQHaR7jq1Y0hhZzWi3IlrRWlnRi0TKVWCZgUwO0PJttNq5wYZPsxgiS-khUCC1qtbKrRgBK_3sefxPkWDOQEubu0Kjyjq4rVZnq66anO3Qw82CSLn0nSCu-AL5Xd4Xchw",
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzIyMzc2MjQsImlhdCI6MTY3MjIzNzMyNCwianRpIjoiMTI2MTBmOWUtMjY1OC00ZmE3LTlkNTItZGU4NzNiZDVjZGE3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI4NzQ5MTFkNS00ZTZlLTRjNmMtYmQxYy1hMzlmZjE2OTI0YWIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.FOZNfhuWutaKUsVnr3A2jxJ6PM7fpX5BdB63SI_3CI7LFgnVmruprQC3ibhYGD77yX59yRWlGlIDlXvybd6v4tujKEL1Kuf4J-MSjj3dJcIx29PtqMe91I49MkIsjr3M24YW4bgOtdbUTYvT1l0IUisW1V_-t23qW_tsbXxviNr_9HSiJYZJZ7a47tmEptJaDZtAwjBaQc8s4BVIqiPbIcYE1Mj1Giu56C3k_v_boSxcl3rrRMXgIWTSO4TtV_jm2UfERzp82B6dZYdq0ZxeyaD0nCzXSkZ41pOeqFK4Qsm6ttCKe5OJ-RGpqvi0KH4YNuhUKHpzisVOL0cPDiWmLg",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -574,6 +574,85 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "getCommentById",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzIyMzc2MjQsImlhdCI6MTY3MjIzNzMyNCwianRpIjoiMTI2MTBmOWUtMjY1OC00ZmE3LTlkNTItZGU4NzNiZDVjZGE3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI4NzQ5MTFkNS00ZTZlLTRjNmMtYmQxYy1hMzlmZjE2OTI0YWIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.FOZNfhuWutaKUsVnr3A2jxJ6PM7fpX5BdB63SI_3CI7LFgnVmruprQC3ibhYGD77yX59yRWlGlIDlXvybd6v4tujKEL1Kuf4J-MSjj3dJcIx29PtqMe91I49MkIsjr3M24YW4bgOtdbUTYvT1l0IUisW1V_-t23qW_tsbXxviNr_9HSiJYZJZ7a47tmEptJaDZtAwjBaQc8s4BVIqiPbIcYE1Mj1Giu56C3k_v_boSxcl3rrRMXgIWTSO4TtV_jm2UfERzp82B6dZYdq0ZxeyaD0nCzXSkZ41pOeqFK4Qsm6ttCKe5OJ-RGpqvi0KH4YNuhUKHpzisVOL0cPDiWmLg",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "undefined",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/pentests/df516de6-ca5e-44a6-ac50-db89bb17aac3/comment",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"pentests",
|
||||||
|
"df516de6-ca5e-44a6-ac50-db89bb17aac3",
|
||||||
|
"comment"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updateComment",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItdG1lbEV0ZHhGTnRSMW9aNXlRdE5jaFFpX0RVN2VNeV9YcU44aXY0S3hzIn0.eyJleHAiOjE2NzA0MTQ3ODYsImlhdCI6MTY3MDQxNDQ4NiwianRpIjoiM2FmOWU5M2MtY2YzNi00MjQwLTkzNWEtNDkxYTJkZTY2MWU4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4ODg4L2F1dGgvcmVhbG1zL2M0cG9fcmVhbG1fbG9jYWwiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTBlMDZkN2EtOGRkMC00ZWNkLTg5NjMtMDU2YjQ1MDc5YzRmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYzRwb19sb2NhbCIsInNlc3Npb25fc3RhdGUiOiI5M2ExNTBlMC03ZWRkLTQxZTgtYWE4Yi0yZWY5YTgzOWU4NDciLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImM0cG9fdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJjNHBvX2xvY2FsIjp7InJvbGVzIjpbInVzZXIiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6InRlc3QgdXNlciIsInByZWZlcnJlZF91c2VybmFtZSI6InR0dCIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJ1c2VyIn0.QjUkCInyCJ5Wsz4q56gfsLqERr6pYlGjwNw-VsKNJ_3Jp-8Dazq9UmDGN8AmAkQ0sp0b-FMm3jArKMBpr84gKd65trvQx_qHvXev5x2MWBG4_9v3C9MmjxWcAYRVmfRdURUOhfto-4YfRwMwNRsKJfwMIjfS5VT8bHJWipcCDzaidN8h_LLORbmmQZ2o0l4Jnv5qrrWzUcSTeEeBpHGOjes1-T0gOlDJa34Z9x_xrsTsybKAylrmX03mDSI-f2h5XqqtgnrxtddtHXHatfxB1BHWq-FILDsGf0UG47FEQjqapFvn9bFiNyq0GVrgdK42miEO7ywOtCOKpCfAUnMwdQ",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "undefined",
|
||||||
|
"type": "any"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "PATCH",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"title\": \"Test Comment\",\n \"description\": \"Edited Test Comment Description\",\n \"relatedFindings\": []\n}",
|
||||||
|
"options": {
|
||||||
|
"raw": {
|
||||||
|
"language": "json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/pentests/df516de6-ca5e-44a6-ac50-db89bb17aac3/comment",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"pentests",
|
||||||
|
"df516de6-ca5e-44a6-ac50-db89bb17aac3",
|
||||||
|
"comment"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -317,20 +317,63 @@ include::{snippets}/deleteCommentByPentestAndCommentId/http-response.adoc[]
|
||||||
|
|
||||||
include::{snippets}/deleteCommentByPentestAndCommentId/response-fields.adoc[]
|
include::{snippets}/deleteCommentByPentestAndCommentId/response-fields.adoc[]
|
||||||
|
|
||||||
|
=== Get comment for id
|
||||||
|
|
||||||
|
To get a comment by id, call the GET request /pentests/+{commentId}+/comment.
|
||||||
|
|
||||||
|
==== Request example
|
||||||
|
|
||||||
|
include::{snippets}/getCommentById/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/getCommentById/path-parameters.adoc[]
|
||||||
|
|
||||||
|
==== Response example
|
||||||
|
|
||||||
|
include::{snippets}/getCommentById/http-response.adoc[]
|
||||||
|
|
||||||
|
==== Response structure
|
||||||
|
|
||||||
|
include::{snippets}/getCommentById/response-fields.adoc[]
|
||||||
|
|
||||||
|
=== Update comment
|
||||||
|
|
||||||
|
To update a comment, call the PATCH request /pentests/+{commentId}+/comment
|
||||||
|
|
||||||
|
==== Request example
|
||||||
|
|
||||||
|
include::{snippets}/updateCommentById/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Request structure
|
||||||
|
|
||||||
|
include::{snippets}/updateCommentById/path-parameters.adoc[]
|
||||||
|
|
||||||
|
==== Response example
|
||||||
|
|
||||||
|
include::{snippets}/updateCommentById/http-response.adoc[]
|
||||||
|
|
||||||
|
==== Response structure
|
||||||
|
|
||||||
|
include::{snippets}/updateCommentById/response-fields.adoc[]
|
||||||
|
|
||||||
|
|
||||||
== Change History
|
== Change History
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|Date |Change
|
|Date |Change
|
||||||
|
|2022-12-28
|
||||||
|
|Added GET, PATCH endpoint for Comment
|
||||||
|2022-12-23
|
|2022-12-23
|
||||||
|Added DELETE endpoint for Comment
|
|Added DELETE endpoint for Comment
|
||||||
|2022-12-22
|
|2022-12-22
|
||||||
|Added GET, POST endpoint for Comment
|
|Added GET, POST endpoint for Comment(s)
|
||||||
|2022-12-09
|
|2022-12-09
|
||||||
|Added DELETE endpoint for Finding
|
|Added DELETE endpoint for Finding
|
||||||
|2022-12-08
|
|2022-12-08
|
||||||
|Added GET and PATCH endpoint for Finding
|
|Added GET and PATCH endpoint for Finding
|
||||||
|2022-12-02
|
|2022-12-02
|
||||||
|Added GET and POST endpoint for Findings
|
|Added GET and POST endpoint for Finding(s)
|
||||||
|2022-11-21
|
|2022-11-21
|
||||||
|Added GET, POST and PATCH endpoint for Pentests
|
|Added GET, POST and PATCH endpoint for Pentests
|
||||||
|2022-03-07
|
|2022-03-07
|
||||||
|
|
|
@ -4,11 +4,7 @@ import com.securityc4po.api.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
|
||||||
import com.securityc4po.api.extensions.getLoggerFor
|
import com.securityc4po.api.extensions.getLoggerFor
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
import com.securityc4po.api.ResponseBody
|
import com.securityc4po.api.ResponseBody
|
||||||
import com.securityc4po.api.configuration.error.handler.EntityNotFoundException
|
|
||||||
import com.securityc4po.api.configuration.error.handler.Errorcode
|
|
||||||
import com.securityc4po.api.pentest.finding.toFindingResponseBody
|
|
||||||
import com.securityc4po.api.pentest.PentestService
|
import com.securityc4po.api.pentest.PentestService
|
||||||
import com.securityc4po.api.pentest.finding.toFindingDeleteResponseBody
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.http.ResponseEntity.noContent
|
import org.springframework.http.ResponseEntity.noContent
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
|
@ -40,6 +36,13 @@ class CommentController(private val pentestService: PentestService, private val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{commentId}/comment")
|
||||||
|
fun getComment(@PathVariable(value = "commentId") commentId: String):Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
return this.commentService.getCommentById(commentId).map {
|
||||||
|
ResponseEntity.ok().body(it.toCommentResponseBody())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/{pentestId}/comment")
|
@PostMapping("/{pentestId}/comment")
|
||||||
fun saveComment(
|
fun saveComment(
|
||||||
@PathVariable(value = "pentestId") pentestId: String,
|
@PathVariable(value = "pentestId") pentestId: String,
|
||||||
|
@ -50,8 +53,18 @@ class CommentController(private val pentestService: PentestService, private val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PatchMapping("/{commentId}/comment")
|
||||||
|
fun updateComment(
|
||||||
|
@PathVariable(value = "commentId") commentId: String,
|
||||||
|
@RequestBody body: CommentRequestBody
|
||||||
|
): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
return this.commentService.updateComment(commentId, body).map {
|
||||||
|
ResponseEntity.accepted().body(it.toCommentResponseBody())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{pentestId}/comment/{commentId}")
|
@DeleteMapping("/{pentestId}/comment/{commentId}")
|
||||||
fun deleteFinding(
|
fun deleteComment(
|
||||||
@PathVariable(value = "pentestId") pentestId: String,
|
@PathVariable(value = "pentestId") pentestId: String,
|
||||||
@PathVariable(value = "commentId") commentId: String
|
@PathVariable(value = "commentId") commentId: String
|
||||||
): Mono<ResponseEntity<ResponseBody>> {
|
): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
|
|
@ -8,10 +8,12 @@ import com.securityc4po.api.configuration.error.handler.InvalidModelException
|
||||||
import com.securityc4po.api.configuration.error.handler.TransactionInterruptedException
|
import com.securityc4po.api.configuration.error.handler.TransactionInterruptedException
|
||||||
import com.securityc4po.api.extensions.getLoggerFor
|
import com.securityc4po.api.extensions.getLoggerFor
|
||||||
import com.securityc4po.api.pentest.PentestService
|
import com.securityc4po.api.pentest.PentestService
|
||||||
|
import com.securityc4po.api.pentest.finding.*
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import reactor.core.publisher.Mono
|
import reactor.core.publisher.Mono
|
||||||
import reactor.kotlin.core.publisher.switchIfEmpty
|
import reactor.kotlin.core.publisher.switchIfEmpty
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION, MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
||||||
|
@ -19,6 +21,36 @@ class CommentService(private val commentRepository: CommentRepository, private v
|
||||||
|
|
||||||
var logger = getLoggerFor<CommentService>()
|
var logger = getLoggerFor<CommentService>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all [Comments]s by commentId's
|
||||||
|
*
|
||||||
|
* @return list of [Comment]s
|
||||||
|
*/
|
||||||
|
fun getCommentsByIds(commentIds: List<String>): Mono<List<Comment>> {
|
||||||
|
return commentRepository.findCommentsByIds(commentIds).collectList().map {
|
||||||
|
it.map { commentEntity -> commentEntity.toComment() }
|
||||||
|
}.switchIfEmpty {
|
||||||
|
val msg = "Comment not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.CommentsNotFound)
|
||||||
|
logger.warn(msg, ex)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get [Comment] by commentId
|
||||||
|
*
|
||||||
|
* @return of [Comment]
|
||||||
|
*/
|
||||||
|
fun getCommentById(commentId: String): Mono<Comment> {
|
||||||
|
return this.commentRepository.findCommentById(commentId).switchIfEmpty {
|
||||||
|
logger.warn("Comment with id $commentId not found.")
|
||||||
|
val msg = "Comment with id $commentId not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.CommentNotFound)
|
||||||
|
throw ex
|
||||||
|
}.map { it.toComment() }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save [Comment]
|
* Save [Comment]
|
||||||
*
|
*
|
||||||
|
@ -38,7 +70,7 @@ class CommentService(private val commentRepository: CommentRepository, private v
|
||||||
val commentEntity = CommentEntity(comment)
|
val commentEntity = CommentEntity(comment)
|
||||||
return commentRepository.insert(commentEntity).flatMap { newCommentEntity: CommentEntity ->
|
return commentRepository.insert(commentEntity).flatMap { newCommentEntity: CommentEntity ->
|
||||||
val comment = newCommentEntity.toComment()
|
val comment = newCommentEntity.toComment()
|
||||||
// After successfully saving finding add id to pentest
|
// After successfully saving comment add id to pentest
|
||||||
pentestService.updatePentestComment(pentestId, comment.id).onErrorMap {
|
pentestService.updatePentestComment(pentestId, comment.id).onErrorMap {
|
||||||
TransactionInterruptedException(
|
TransactionInterruptedException(
|
||||||
"Pentest could not be updated in Database.",
|
"Pentest could not be updated in Database.",
|
||||||
|
@ -59,18 +91,39 @@ class CommentService(private val commentRepository: CommentRepository, private v
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all [Comments]s by commentId's
|
* Update [Comment]
|
||||||
*
|
*
|
||||||
* @return list of [Comment]s
|
* @throws [InvalidModelException] if the [Comment] is invalid
|
||||||
|
* @throws [TransactionInterruptedException] if the [Comment] could not be stored
|
||||||
|
* @return saved [Comment]
|
||||||
*/
|
*/
|
||||||
fun getCommentsByIds(commentIds: List<String>): Mono<List<Comment>> {
|
fun updateComment(commentId: String, body: CommentRequestBody): Mono<Comment> {
|
||||||
return commentRepository.findCommentsByIds(commentIds).collectList().map {
|
validate(
|
||||||
it.map { commentEntity -> commentEntity.toComment() }
|
require = body.isValid(),
|
||||||
}.switchIfEmpty {
|
logging = { logger.warn("Comment not valid.") },
|
||||||
val msg = "Comment not found."
|
mappedException = InvalidModelException(
|
||||||
val ex = EntityNotFoundException(msg, Errorcode.CommentsNotFound)
|
"Comment not valid.", Errorcode.CommentInvalid
|
||||||
logger.warn(msg, ex)
|
)
|
||||||
|
)
|
||||||
|
return this.commentRepository.findCommentById(commentId).switchIfEmpty {
|
||||||
|
logger.warn("Comment with id $commentId not found.")
|
||||||
|
val msg = "Comment with id $commentId not found."
|
||||||
|
val ex = EntityNotFoundException(msg, Errorcode.CommentNotFound)
|
||||||
throw ex
|
throw ex
|
||||||
|
}.flatMap { currentCommentEntity: CommentEntity ->
|
||||||
|
currentCommentEntity.lastModified = Instant.now()
|
||||||
|
currentCommentEntity.data = buildComment(body, currentCommentEntity)
|
||||||
|
commentRepository.save(currentCommentEntity).map {
|
||||||
|
it.toComment()
|
||||||
|
}
|
||||||
|
}.doOnError {
|
||||||
|
throw wrappedException(
|
||||||
|
logging = { logger.warn("Comment could not be updated in Database. Thrown exception: ", it) },
|
||||||
|
mappedException = TransactionInterruptedException(
|
||||||
|
"Comment could not be stored.",
|
||||||
|
Errorcode.CommentInsertionFailed
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,54 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class GetComment {
|
||||||
|
@Test
|
||||||
|
fun getCommentById() {
|
||||||
|
val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
|
||||||
|
webTestClient.get()
|
||||||
|
.uri("/pentests/{commentId}/comment", commentId)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk
|
||||||
|
.expectHeader().doesNotExist("")
|
||||||
|
.expectBody().json(Json.write(commentOne.toCommentResponseBody()))
|
||||||
|
.consumeWith(
|
||||||
|
WebTestClientRestDocumentation.document(
|
||||||
|
"{methodName}",
|
||||||
|
Preprocessors.preprocessRequest(
|
||||||
|
Preprocessors.prettyPrint(),
|
||||||
|
Preprocessors.modifyUris().removePort(),
|
||||||
|
Preprocessors.removeHeaders("Host", "Content-Length")
|
||||||
|
),
|
||||||
|
Preprocessors.preprocessResponse(
|
||||||
|
Preprocessors.prettyPrint()
|
||||||
|
),
|
||||||
|
RequestDocumentation.relaxedPathParameters(
|
||||||
|
RequestDocumentation.parameterWithName("commentId").description("The id of the comment you want to get")
|
||||||
|
),
|
||||||
|
PayloadDocumentation.relaxedResponseFields(
|
||||||
|
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
||||||
|
.description("The id of the requested comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
|
.description("The title of the requested comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
||||||
|
.description("The description number of the comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
|
||||||
|
.description("List of related findings of the comment")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commentOne = Comment(
|
||||||
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
|
title = "Found Bug",
|
||||||
|
description = "OTG-INFO-002 Bug",
|
||||||
|
relatedFindings = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class SaveComment {
|
inner class SaveComment {
|
||||||
@Test
|
@Test
|
||||||
|
@ -149,6 +197,54 @@ class CommentControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class UpdateFinding {
|
||||||
|
@Test
|
||||||
|
fun updateCommentById() {
|
||||||
|
val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
|
||||||
|
webTestClient.patch()
|
||||||
|
.uri("/pentests/{commentId}/comment", commentId)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.body(Mono.just(commentBody), CommentRequestBody::class.java)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isAccepted
|
||||||
|
.expectHeader().doesNotExist("")
|
||||||
|
.expectBody().json(Json.write(commentBody))
|
||||||
|
.consumeWith(
|
||||||
|
WebTestClientRestDocumentation.document(
|
||||||
|
"{methodName}",
|
||||||
|
Preprocessors.preprocessRequest(
|
||||||
|
Preprocessors.prettyPrint(),
|
||||||
|
Preprocessors.modifyUris().removePort(),
|
||||||
|
Preprocessors.removeHeaders("Host", "Content-Length")
|
||||||
|
),
|
||||||
|
Preprocessors.preprocessResponse(
|
||||||
|
Preprocessors.prettyPrint()
|
||||||
|
),
|
||||||
|
RequestDocumentation.relaxedPathParameters(
|
||||||
|
RequestDocumentation.parameterWithName("commentId").description("The id of the comment you want to update")
|
||||||
|
),
|
||||||
|
PayloadDocumentation.relaxedResponseFields(
|
||||||
|
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
||||||
|
.description("The id of the updated comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
|
||||||
|
.description("The title of the requested comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("description").type(JsonFieldType.STRING)
|
||||||
|
.description("The description number of the comment"),
|
||||||
|
PayloadDocumentation.fieldWithPath("relatedFindings").type(JsonFieldType.ARRAY)
|
||||||
|
.description("List of related findings of the comment")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commentBody = CommentRequestBody(
|
||||||
|
title = "Updated Comment",
|
||||||
|
description = "Updated Description",
|
||||||
|
relatedFindings = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class DeleteComment {
|
inner class DeleteComment {
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -29,7 +29,7 @@ import java.time.Duration
|
||||||
NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
|
NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR,
|
||||||
RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
|
RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
|
||||||
)
|
)
|
||||||
class CommentControllerIntegrationTest: BaseIntTest() {
|
class CommentControllerIntTest : BaseIntTest() {
|
||||||
|
|
||||||
@LocalServerPort
|
@LocalServerPort
|
||||||
private var port = 0
|
private var port = 0
|
||||||
|
@ -85,6 +85,28 @@ class CommentControllerIntegrationTest: BaseIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class GetComment {
|
||||||
|
@Test
|
||||||
|
fun `requesting comment by commentId successfully`() {
|
||||||
|
val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
|
||||||
|
webTestClient.get()
|
||||||
|
.uri("/pentests/{commentId}/comment", commentId)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody().json(Json.write(commentOne.toCommentResponseBody()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commentOne = Comment(
|
||||||
|
id = "ab62d365-1b1d-4da1-89bc-5496616e220f",
|
||||||
|
title = "Found Bug",
|
||||||
|
description = "OTG-INFO-002 Bug",
|
||||||
|
relatedFindings = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class SaveComment {
|
inner class SaveComment {
|
||||||
@Test
|
@Test
|
||||||
|
@ -110,6 +132,31 @@ class CommentControllerIntegrationTest: BaseIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class UpdateComment {
|
||||||
|
@Test
|
||||||
|
fun `update comment successfully`() {
|
||||||
|
val commentId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
|
||||||
|
webTestClient.patch()
|
||||||
|
.uri("/pentests/{commentId}/comment", commentId)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.body(Mono.just(commentBody), CommentRequestBody::class.java)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isAccepted
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody()
|
||||||
|
.jsonPath("$.title").isEqualTo("Updated Comment")
|
||||||
|
.jsonPath("$.description").isEqualTo("Updated Description")
|
||||||
|
.jsonPath("$.relatedFindings").isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
private val commentBody = CommentRequestBody(
|
||||||
|
title = "Updated Comment",
|
||||||
|
description = "Updated Description",
|
||||||
|
relatedFindings = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
inner class DeleteComment {
|
inner class DeleteComment {
|
||||||
@Test
|
@Test
|
|
@ -137,7 +137,7 @@ class FindingControllerDocumentationTest: BaseDocumentationIntTest() {
|
||||||
Preprocessors.prettyPrint()
|
Preprocessors.prettyPrint()
|
||||||
),
|
),
|
||||||
RequestDocumentation.relaxedPathParameters(
|
RequestDocumentation.relaxedPathParameters(
|
||||||
RequestDocumentation.parameterWithName("findingId").description("The id of the feinidng you want to get")
|
RequestDocumentation.parameterWithName("findingId").description("The id of the finding you want to get")
|
||||||
),
|
),
|
||||||
PayloadDocumentation.relaxedResponseFields(
|
PayloadDocumentation.relaxedResponseFields(
|
||||||
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
|
||||||
|
|
|
@ -152,8 +152,8 @@ class FindingControllerIntTest: BaseIntTest() {
|
||||||
inner class UpdateFinding {
|
inner class UpdateFinding {
|
||||||
@Test
|
@Test
|
||||||
fun `update finding successfully`() {
|
fun `update finding successfully`() {
|
||||||
val findingId = "43fbc63c-f624-11ec-b939-0242ac120002"
|
val findingId = "ab62d365-1b1d-4da1-89bc-5496616e220f"
|
||||||
webTestClient.post()
|
webTestClient.patch()
|
||||||
.uri("/pentests/{findingId}/finding", findingId)
|
.uri("/pentests/{findingId}/finding", findingId)
|
||||||
.header("Authorization", "Bearer $tokenAdmin")
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
.body(Mono.just(findingBody), FindingRequestBody::class.java)
|
.body(Mono.just(findingBody), FindingRequestBody::class.java)
|
||||||
|
|
Loading…
Reference in New Issue