feat: added dialog-service and new project dialog

This commit is contained in:
Marcel Haag 2021-11-17 15:30:52 +01:00
parent a54f064692
commit 8dc287fc8a
25 changed files with 598 additions and 82 deletions

View File

@ -6,7 +6,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {
NbLayoutModule,
NbToastrModule,
NbIconModule, NbCardModule, NbButtonModule,
NbIconModule, NbCardModule, NbButtonModule, NbDialogService, NbDialogModule,
} from '@nebular/theme';
import {NbEvaIconsModule} from '@nebular/eva-icons';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
@ -26,6 +26,7 @@ import {HomeModule} from './home/home.module';
import {KeycloakService} from 'keycloak-angular';
import {httpInterceptorProviders} from '@shared/interceptors';
import {FlexLayoutModule} from '@angular/flex-layout';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
@NgModule({
declarations: [
@ -53,6 +54,7 @@ import {FlexLayoutModule} from '@angular/flex-layout';
deps: [HttpClient]
}
}),
NbDialogModule.forRoot(),
HeaderModule,
HomeModule,
FlexLayoutModule
@ -67,7 +69,9 @@ import {FlexLayoutModule} from '@angular/flex-layout';
},
KeycloakService,
httpInterceptorProviders,
NotificationService
NotificationService,
NbDialogService,
DialogService
],
bootstrap: [
AppComponent

View File

@ -48,7 +48,6 @@ describe('LoginComponent', () => {
BrowserAnimationsModule,
ReactiveFormsModule,
NbInputModule,
NbCardModule,
NbButtonModule,
NbLayoutModule,
ThemeModule.forRoot(),

View File

@ -22,6 +22,8 @@ import {ProjectServiceMock} from '@shared/services/project.service.mock';
import {ThemeModule} from '@assets/@theme/theme.module';
import {LoadingSpinnerComponent} from '@shared/widgets/loading-spinner/loading-spinner.component';
import {KeycloakService} from 'keycloak-angular';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
describe('ProjectOverviewComponent', () => {
let component: ProjectOverviewComponent;
@ -61,6 +63,7 @@ describe('ProjectOverviewComponent', () => {
providers: [
KeycloakService,
{provide: ProjectService, useValue: new ProjectServiceMock()},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NotificationService, useValue: new NotificationServiceMock()}
]
})

View File

@ -1,11 +1,14 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import * as FA from '@fortawesome/free-solid-svg-icons';
import {Project} from '@shared/models/project.model';
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
import {BehaviorSubject, Observable} from 'rxjs';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {ProjectService} from '@shared/services/project.service';
import {NotificationService, PopupType} from '@shared/services/notification.service';
import {tap} from 'rxjs/operators';
import {filter, mergeMap, tap} from 'rxjs/operators';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {NB_DIALOG_CONFIG} from '@nebular/theme/components/dialog/dialog-config';
@Component({
selector: 'app-project-overview',
@ -21,20 +24,21 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
constructor(
private readonly projectService: ProjectService,
private readonly dialogService: DialogService,
private readonly notificationService: NotificationService) {
}
ngOnInit(): void {
this.getProjects();
this.loadProjects();
}
getProjects(): void {
loadProjects(): void {
this.projectService.getProjects()
.pipe(
untilDestroyed(this),
tap(() => this.loading$.next(true))
)
.subscribe( {
.subscribe({
next: (projects) => {
this.projects.next(projects);
this.loading$.next(false);
@ -48,7 +52,28 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy {
}
onClickAddProject(): void {
console.log('to be implemented...');
this.dialogService.openCustomDialog(
ProjectDialogComponent,
{
closeOnEsc: false,
hasScroll: false,
autoFocus: false,
closeOnBackdropClick: false
}
).onClose.pipe(
filter(value => !!value),
mergeMap((value: SaveProjectDialogBody) => this.projectService.saveProject(value)),
untilDestroyed(this)
).subscribe({
next: () => {
this.loadProjects();
this.notificationService.showPopup('project.popup.save.success', PopupType.SUCCESS);
},
error: error => {
console.error(error);
this.notificationService.showPopup('project.popup.save.failed', PopupType.FAILURE);
}
});
}
onClickEditProject(): void {

View File

@ -2,12 +2,15 @@ import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ProjectOverviewComponent} from './project-overview.component';
import {ProjectOverviewRoutingModule} from './project-overview-routing.module';
import {NbButtonModule, NbCardModule, NbProgressBarModule} from '@nebular/theme';
import {NbButtonModule, NbCardModule, NbDialogService, NbProgressBarModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core';
import {DateTimeFormatPipe} from '@shared/pipes/date-time-format.pipe';
import {ProjectModule} from './project';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ProjectDialogModule} from '@shared/modules/project-dialog/project-dialog.module';
@NgModule({
declarations: [
@ -23,7 +26,12 @@ import {ProjectModule} from './project';
FontAwesomeModule,
TranslateModule,
NbProgressBarModule,
ProjectModule
ProjectModule,
ProjectDialogModule
],
providers: [
DialogService,
NbDialogService
]
})
export class ProjectOverviewModule {

View File

@ -2,6 +2,8 @@
"global": {
"action.login": "Einloggen",
"action.retry": "Erneut Versuchen",
"action.save": "Speichern",
"action.cancel": "Abbrechen",
"username": "Nutzername",
"password": "Passwort"
},
@ -28,9 +30,18 @@
"add.project": "Projekt hinzufügen",
"no.projects": "Keine Projekte verfügbar"
},
"popup": {
"not.found": "Keine Projekte gefunden"
"create": {
"header": "Neues Projekt erstellen"
},
"popup": {
"not.found": "Keine Projekte gefunden",
"save.success": "Projekt erfolgreich gespeichert",
"save.failed": "Projekt konnte nicht gespeichert werden"
},
"title.label": "Projekt Titel",
"client.label": "Name des Auftraggebers",
"tester.label": "Name des Pentester",
"title": "Titel",
"client": "Klient",
"tester": "Tester",
"createdAt": "Erstellt am"

View File

@ -2,6 +2,8 @@
"global": {
"action.login": "Login",
"action.retry": "Try again",
"action.save": "Save",
"action.cancel": "Cancel",
"username": "Username",
"password": "Password"
},
@ -28,9 +30,18 @@
"add.project": "Add project",
"no.projects": "No projects available"
},
"popup": {
"not.found": "No projects found"
"create": {
"header": "Create New Project"
},
"popup": {
"not.found": "No projects found",
"save.success": "Project saved successfully",
"save.failed": "Project could not be saved"
},
"title.label": "Project Title",
"client.label": "Name of Client",
"tester.label": "Name of Pentester",
"title": "Title",
"client": "Client",
"tester": "Tester",
"createdAt": "Created at"

View File

@ -20,3 +20,9 @@ export class Project {
this.createdBy = createdBy;
}
}
export interface SaveProjectDialogBody {
title: string;
client: string;
tester: string;
}

View File

@ -0,0 +1,49 @@
<nb-card #dialog accent="primary" class="project-dialog">
<nb-card-header fxLayoutAlign="start center" class="project-dialog-header">
{{ 'project.create.header' | translate }}
</nb-card-header>
<nb-card-body>
<form [formGroup]="projectFormGroup" fxLayout="column" fxLayoutGap="1rem" fxLayoutAlign="start start">
<nb-form-field class="project-form-field">
<label for="projectTitleInput" class="label">
{{'project.title.label' | translate}}:
</label>
<input formControlName="projectTitle" required
id="projectTitleInput" nbInput
class="input" type="text" fullWidth
status="{{formCtrlStatus}}"
placeholder="{{'project.title' | translate}} *">
</nb-form-field>
<nb-form-field class="project-form-field">
<label for="projectClientInput" class="label">
{{'project.client.label' | translate}}:
</label>
<input formControlName="projectClient" required
id="projectClientInput" nbInput
class="input" type="text" fullWidth
status="{{formCtrlStatus}}"
placeholder="{{'project.client' | translate}} *">
</nb-form-field>
<nb-form-field class="project-form-field">
<label for="projectTesterInput" class="label">
{{'project.tester.label' | translate}}:
</label>
<input formControlName="projectTester" required
id="projectTesterInput" nbInput
class="input" type="text" fullWidth
status="{{formCtrlStatus}}"
placeholder="{{'project.tester' | translate}} *">
</nb-form-field>
</form>
</nb-card-body>
<nb-card-footer fxLayout="row" fxLayoutGap="1.5rem" fxLayoutAlign="end end">
<button nbButton status="success" [disabled]="formIsEmptyOrInvalid()" (click)="onClickSave(projectFormGroup.value)">
{{ 'global.action.save' | translate}}
</button>
<button nbButton status="danger" (click)="onClickClose()">
{{ 'global.action.cancel' | translate }}
</button>
</nb-card-footer>
</nb-card>

View File

@ -0,0 +1,23 @@
.project-dialog {
width: 24rem;
height: 31rem;
.project-dialog-header {
height: 8vh;
font-size: 1.5rem;
}
nb-form-field {
padding: 0.5rem 0 0.75rem;
}
.label {
display: block;
font-size: 0.95rem;
padding-bottom: 0.5rem;
}
.input {
width: 15rem;
}
}

View File

@ -0,0 +1,66 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ProjectDialogComponent} from './project-dialog.component';
import {CommonModule} from '@angular/common';
import {NbButtonModule, NbCardModule, NbDialogRef, NbFormFieldModule, NbInputModule, NbLayoutModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {ThemeModule} from '@assets/@theme/theme.module';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {HttpLoaderFactory} from '../../../app/common-app.module';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NotificationService} from '@shared/services/notification.service';
import {NotificationServiceMock} from '@shared/services/notification.service.mock';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
import {ReactiveFormsModule} from '@angular/forms';
describe('ProjectDialogComponent', () => {
let component: ProjectDialogComponent;
let fixture: ComponentFixture<ProjectDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
ProjectDialogComponent
],
imports: [
CommonModule,
NbLayoutModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
NbInputModule,
NbFormFieldModule,
ReactiveFormsModule,
BrowserAnimationsModule,
ThemeModule.forRoot(),
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
HttpClientModule,
HttpClientTestingModule
],
providers: [
{provide: NotificationService, useValue: new NotificationServiceMock()},
{provide: DialogService, useClass: DialogServiceMock},
{provide: NbDialogRef, useValue: {}}
]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProjectDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,82 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {NbDialogRef} from '@nebular/theme';
import {AbstractControl, FormBuilder, FormGroup, Validators} from '@angular/forms';
import {FieldStatus} from '@shared/models/form-field-status.model';
import {untilDestroyed} from 'ngx-take-until-destroy';
@Component({
selector: 'app-project-dialog',
templateUrl: './project-dialog.component.html',
styleUrls: ['./project-dialog.component.scss']
})
export class ProjectDialogComponent implements OnInit, OnDestroy {
// form control elements
projectFormGroup: FormGroup;
projectTitleCtrl: AbstractControl;
projectClientCtrl: AbstractControl;
projectTesterCtrl: AbstractControl;
formCtrlStatus = FieldStatus.BASIC;
invalidProjectTitle: string;
invalidProjectClient: string;
invalidProjectTester: string;
readonly MIN_LENGTH: number = 2;
constructor(
private fb: FormBuilder,
protected dialogRef: NbDialogRef<ProjectDialogComponent>
) {
}
ngOnInit(): void {
this.projectFormGroup = this.fb.group({
projectTitle: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
projectClient: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]],
projectTester: ['', [Validators.required, Validators.minLength(this.MIN_LENGTH)]]
});
this.projectTitleCtrl = this.projectFormGroup.get('projectTitle');
this.projectClientCtrl = this.projectFormGroup.get('projectClient');
this.projectTesterCtrl = this.projectFormGroup.get('projectTester');
this.projectFormGroup.valueChanges
.pipe(untilDestroyed(this))
.subscribe(() => {
this.formCtrlStatus = FieldStatus.BASIC;
});
}
onClickSave(value): void {
this.dialogRef.close({
title: value.projectTitle,
client: value.projectClient,
tester: value.projectTester
});
}
onClickClose(): void {
this.dialogRef.close();
}
formIsEmptyOrInvalid(): boolean {
return this.isEmpty(this.projectTitleCtrl.value)
|| this.isEmpty(this.projectClientCtrl.value)
|| this.isEmpty(this.projectTesterCtrl.value)
|| this.projectTitleCtrl.invalid
|| this.projectClientCtrl.invalid
|| this.projectTesterCtrl.invalid;
}
/**
* @param ctrlValue of type string
* @return if ctrlValue is empty or not
*/
isEmpty(ctrlValue: string): boolean {
return ctrlValue === '';
}
ngOnDestroy(): void {
}
}

View File

@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {ProjectDialogComponent} from '@shared/modules/project-dialog/project-dialog.component';
import {NbButtonModule, NbCardModule, NbDialogService, NbFormFieldModule, NbInputModule} from '@nebular/theme';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
import {TranslateModule} from '@ngx-translate/core';
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ReactiveFormsModule} from '@angular/forms';
@NgModule({
declarations: [
ProjectDialogComponent
],
imports: [
CommonModule,
NbCardModule,
NbButtonModule,
FlexLayoutModule,
FontAwesomeModule,
TranslateModule,
ReactiveFormsModule,
NbFormFieldModule,
NbInputModule,
],
providers: [
DialogService,
NbDialogService
],
entryComponents: [
ProjectDialogComponent
]
})
export class ProjectDialogModule { }

View File

@ -0,0 +1,16 @@
import {DialogService} from '@shared/services/dialog-service/dialog.service';
import {ComponentType} from '@angular/cdk/overlay';
import {TemplateRef} from '@angular/core';
import {NbDialogConfig, NbDialogRef} from '@nebular/theme';
export class DialogServiceMock implements Required<DialogService> {
dialog: any;
openCustomDialog<T, D = any, R = any>(
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
config?: Partial<NbDialogConfig<Partial<T> | string>>
): NbDialogRef<T> {
return null;
}
}

View File

@ -0,0 +1,30 @@
import {TestBed} from '@angular/core/testing';
import {DialogService} from './dialog.service';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NbDialogModule, NbDialogRef} from '@nebular/theme';
import {DialogServiceMock} from '@shared/services/dialog-service/dialog.service.mock';
describe('DialogService', () => {
let service: DialogService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
NbDialogModule.forRoot()
],
providers: [
{provide: DialogService, useClass: DialogServiceMock},
{provide: NbDialogRef, useValue: {}},
]
});
service = TestBed.inject(DialogService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,28 @@
import {Injectable, TemplateRef} from '@angular/core';
import {NbDialogConfig, NbDialogRef, NbDialogService} from '@nebular/theme';
import {ComponentType} from '@angular/cdk/overlay';
@Injectable({
providedIn: 'root'
})
export class DialogService {
constructor(private dialog: NbDialogService) {
}
/**
* Opens a custom MatDialog
*/
openCustomDialog<T>(
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
config?: Partial<NbDialogConfig<Partial<T> | string>>
): NbDialogRef<T> {
return this.dialog.open<T>(componentOrTemplateRef, {
context: config?.context || undefined,
closeOnEsc: config?.closeOnEsc || false,
hasScroll: config?.hasScroll || false,
autoFocus: config?.autoFocus || false,
closeOnBackdropClick: config?.closeOnBackdropClick || false,
});
}
}

View File

@ -17,7 +17,7 @@ export class NotificationService {
.subscribe((translationContainer) => {
this.toastrService.show(
'',
translationContainer[popupType] + ' ' + translationContainer[translationKey], {
translationContainer[translationKey] + ' ' + translationContainer[popupType], {
position: NbGlobalPhysicalPosition.BOTTOM_RIGHT,
duration: 5000,
toastClass: createCssClassName(popupType)

View File

@ -11,4 +11,8 @@ export class ProjectServiceMock implements Required<ProjectService> {
getProjects(): Observable<Project[]> {
return of([]);
}
saveProject(): Observable<Project> {
return of();
}
}

View File

@ -1,12 +1,18 @@
import { TestBed } from '@angular/core/testing';
import {TestBed} from '@angular/core/testing';
import { ProjectService } from './project.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {ProjectService} from './project.service';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {KeycloakService} from 'keycloak-angular';
import {Project, SaveProjectDialogBody} from '@shared/models/project.model';
import {environment} from '../../environments/environment';
describe('ProjectService', () => {
let service: ProjectService;
let httpMock: HttpTestingController;
const apiBaseURL = `${environment.apiEndpoint}/projects`;
const dummyDate = new Date('2019-01-10T09:00:00');
beforeEach(() => {
TestBed.configureTestingModule({
@ -19,9 +25,89 @@ describe('ProjectService', () => {
]
});
service = TestBed.inject(ProjectService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('getProjects', () => {
const mockProject: Project = {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate,
tester: 'Novatester',
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
const httpResponse = [{
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate,
tester: 'Novatester',
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
}];
it('should get Projects', (done) => {
service.getProjects().subscribe((projects) => {
expect(projects[0].id).toEqual(mockProject.id);
expect(projects[0].client).toEqual(mockProject.client);
expect(projects[0].title).toEqual(mockProject.title);
expect(projects[0].createdAt).toBe(mockProject.createdAt);
expect(projects[0].tester).toEqual(mockProject.tester);
expect(projects[0].createdBy).toEqual(mockProject.createdBy);
done();
});
const mockReq = httpMock.expectOne(`${apiBaseURL}`);
expect(mockReq.cancelled).toBe(false);
expect(mockReq.request.responseType).toEqual('json');
mockReq.flush(httpResponse);
httpMock.verify();
});
});
describe('saveProject', () => {
const mockSaveProjectDialogBody: SaveProjectDialogBody = {
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
tester: 'Novatester',
};
const mockProject: Project = {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate,
tester: 'Novatester',
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
const httpResponse = {
id: '56c47c56-3bcd-45f1-a05b-c197dbd33111',
client: 'E Corp',
title: 'Some Mock API (v1.0) Scanning',
createdAt: dummyDate,
tester: 'Novatester',
createdBy: '11c47c56-3bcd-45f1-a05b-c197dbd33110'
};
it('should save project', (done) => {
service.saveProject(mockSaveProjectDialogBody).subscribe(
value => {
expect(value).toEqual(mockProject);
done();
},
fail);
const req = httpMock.expectOne(`${apiBaseURL}`);
expect(req.request.method).toBe('POST');
req.flush(mockProject);
});
});
});

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import {Injectable} from '@angular/core';
import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http';
import {Project} from '../models/project.model';
import {Project, SaveProjectDialogBody} from '../models/project.model';
import {Observable} from 'rxjs';
@Injectable({
@ -17,4 +17,12 @@ export class ProjectService {
public getProjects(): Observable<Project[]> {
return this.http.get<Project[]>(`${this.apiBaseURL}`);
}
/**
* Save Project
* @param project the information of the project
*/
public saveProject(project: SaveProjectDialogBody): Observable<Project> {
return this.http.post<Project>(`${this.apiBaseURL}`, project);
}
}

View File

@ -1,6 +1,6 @@
{
"info": {
"_postman_id": "58021f5f-0ae9-4f64-990b-f09dcc2d3bc2",
"_postman_id": "a20516f0-5f7f-4d15-9f26-ada358993ff8",
"name": "security-c4po-api",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
@ -71,7 +71,7 @@
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\",\n \"createyBy\": \"10e06d7a-8dd0-4ecd-8963-056b45079c4f\"\n}",
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\"\n}",
"options": {
"raw": {
"language": "json"

View File

@ -41,8 +41,7 @@ fun ProjectOverview.toProjectOverviewResponseBody(): ResponseBody {
data class ProjectRequestBody(
val client: String,
val title: String,
val tester: String? = null,
val createdBy: String
val tester: String? = null
)
fun ProjectRequestBody.toProject(): Project {
@ -52,6 +51,7 @@ fun ProjectRequestBody.toProject(): Project {
title = this.title,
createdAt = Instant.now().toString(),
tester = this.tester,
createdBy = this.createdBy
// ToDo: Should be changed to SUB from Token after adding AUTH Header
createdBy = UUID.randomUUID().toString()
)
}

View File

@ -7,6 +7,7 @@ import com.securityc4po.api.ResponseBody
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import reactor.core.publisher.Mono
import java.util.*
@RestController
@RequestMapping("/projects")

View File

@ -50,22 +50,33 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.expectStatus().isOk
.expectHeader().doesNotExist("")
.expectBody().json(Json.write(getProjectsResponse()))
.consumeWith(WebTestClientRestDocumentation.document("{methodName}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint(),
.consumeWith(
WebTestClientRestDocumentation.document(
"{methodName}",
Preprocessors.preprocessRequest(
Preprocessors.prettyPrint(),
Preprocessors.modifyUris().removePort(),
Preprocessors.removeHeaders("Host", "Content-Length")),
Preprocessors.removeHeaders("Host", "Content-Length")
),
Preprocessors.preprocessResponse(
Preprocessors.prettyPrint()
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING).description("The id of the requested project"),
PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING).description("The name of the client of the requested project"),
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING).description("The title of the requested project"),
PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING).description("The date where the project was created at"),
PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING).description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING).description("The id of the user that created the project")
PayloadDocumentation.fieldWithPath("[].id").type(JsonFieldType.STRING)
.description("The id of the requested project"),
PayloadDocumentation.fieldWithPath("[].client").type(JsonFieldType.STRING)
.description("The name of the client of the requested project"),
PayloadDocumentation.fieldWithPath("[].title").type(JsonFieldType.STRING)
.description("The title of the requested project"),
PayloadDocumentation.fieldWithPath("[].createdAt").type(JsonFieldType.STRING)
.description("The date where the project was created at"),
PayloadDocumentation.fieldWithPath("[].tester").type(JsonFieldType.STRING)
.description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("[].createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project")
)
)
)
))
}
val projectOne = Project(
@ -102,29 +113,39 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
.expectStatus().isAccepted
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
.expectBody().json(Json.write(project))
.consumeWith(WebTestClientRestDocumentation.document("{methodName}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint(),
.consumeWith(
WebTestClientRestDocumentation.document(
"{methodName}",
Preprocessors.preprocessRequest(
Preprocessors.prettyPrint(),
Preprocessors.modifyUris().removePort(),
Preprocessors.removeHeaders("Host", "Content-Length")),
Preprocessors.removeHeaders("Host", "Content-Length")
),
Preprocessors.preprocessResponse(
Preprocessors.prettyPrint()
),
PayloadDocumentation.relaxedResponseFields(
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING).description("The id of the requested project"),
PayloadDocumentation.fieldWithPath("client").type(JsonFieldType.STRING).description("The name of the client of the requested project"),
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING).description("The title of the requested project"),
PayloadDocumentation.fieldWithPath("createdAt").type(JsonFieldType.STRING).description("The date where the project was created at"),
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING).description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING).description("The id of the user that created the project")
PayloadDocumentation.fieldWithPath("id").type(JsonFieldType.STRING)
.description("The id of the requested project"),
PayloadDocumentation.fieldWithPath("client").type(JsonFieldType.STRING)
.description("The name of the client of the requested project"),
PayloadDocumentation.fieldWithPath("title").type(JsonFieldType.STRING)
.description("The title of the requested project"),
PayloadDocumentation.fieldWithPath("createdAt").type(JsonFieldType.STRING)
.description("The date where the project was created at"),
PayloadDocumentation.fieldWithPath("tester").type(JsonFieldType.STRING)
.description("The user that is assigned as a tester in the project"),
PayloadDocumentation.fieldWithPath("createdBy").type(JsonFieldType.STRING)
.description("The id of the user that created the project")
)
)
)
))
}
val project = ProjectRequestBody(
client = "Novatec",
title = "log4j Pentest",
tester = "Stipe",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
tester = "Stipe"
)
}

View File

@ -98,11 +98,12 @@ class ProjectControllerIntTest : BaseIntTest() {
.exchange()
.expectStatus().isAccepted
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
.expectBody().json(Json.write(project))
.expectBody()
.jsonPath("$.client").isEqualTo("Novatec")
.jsonPath("$.title").isEqualTo("log4j Pentest")
.jsonPath("$.tester").isEqualTo("Stipe")
.jsonPath("$.createdBy").isEqualTo("f8aab31f-4925-4242-a6fa-f98135b4b032")
// ToDo: Should be changed to SUB from Token after adding AUTH Header
/*.jsonPath("$.createdBy").isEqualTo("f8aab31f-4925-4242-a6fa-f98135b4b032")*/
}
val project = Project(
@ -111,7 +112,7 @@ class ProjectControllerIntTest : BaseIntTest() {
title = "log4j Pentest",
createdAt = "2021-04-10T18:05:00Z",
tester = "Stipe",
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
createdBy = "a8891ad2-5cf5-4519-a89e-9ef8eec9e10c"
)
}