feat: added Header, Project Model, Service, Controller and Testdata

This commit is contained in:
Marcel Haag 2021-02-14 20:00:38 +01:00
parent 4f4f243518
commit 41e375c32e
60 changed files with 1027 additions and 42 deletions

View File

@ -25,6 +25,7 @@
"aot": true,
"assets": [
"src/favicon.ico",
"src/favicon-c4po.ico",
"src/assets"
],
"styles": [
@ -96,6 +97,7 @@
"tsConfig": "tsconfig.spec.json",
"assets": [
"src/favicon.ico",
"src/favicon-c4po.ico",
"src/assets"
],
"styles": [

View File

@ -5,10 +5,17 @@ import {AuthGuardService} from '../shared/guards/auth-guard.service';
import {LoginGuardService} from '../shared/guards/login-guard.service';
export const START_PAGE = 'home';
export const FALLBACK_PAGE = 'home';
const routes: Routes = [
{
path: 'home', component: HomeComponent,
path: 'home',
component: HomeComponent,
canActivate: [AuthGuardService]
},
{
path: 'dashboard',
loadChildren: () => import('./dashboard').then(mod => mod.DashboardModule),
canActivate: [AuthGuardService]
},
{

View File

@ -1,9 +1,14 @@
<div class="content-container">
<div class="scrollable-content">
<nb-layout>
<nb-layout-column>
<nb-layout>
<nb-layout-header *ngIf="$authState.getValue()">
<app-header></app-header>
</nb-layout-header>
<nb-layout-column>
<div class="content-container">
<div class="scrollable-content">
<router-outlet></router-outlet>
</nb-layout-column>
</nb-layout>
</div>
</div>
</div>
</div>
</nb-layout-column>
</nb-layout>

View File

@ -0,0 +1,15 @@
@import "../assets/@theme/styles/_variables.scss";
.content-container {
width: 90vw;
height: calc(90vh - #{$header-height});
.scrollable-content {
width: 100%;
max-width: 100vw;
height: 100%;
max-height: calc(100vh - #{$header-height});
overflow: auto;
}
}

View File

@ -10,6 +10,7 @@ import {ThemeModule} from '../assets/@theme/theme.module';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {SessionState} from '../shared/stores/session-state/session-state';
import {NgxsModule} from '@ngxs/store';
import {HeaderModule} from './header/header.module';
describe('AppComponent', () => {
beforeEach(async () => {
@ -27,6 +28,7 @@ describe('AppComponent', () => {
}),
NbEvaIconsModule,
ThemeModule,
HeaderModule,
NgxsModule.forRoot([SessionState]),
HttpClientTestingModule
],

View File

@ -4,16 +4,18 @@ import localeDe from '@angular/common/locales/de';
import {registerLocale} from 'i18n-iso-countries';
import {registerLocaleData} from '@angular/common';
import {Store} from '@ngxs/store';
import {BehaviorSubject, of, Subscription} from 'rxjs';
import {SessionState} from '../shared/stores/session-state/session-state';
import { untilDestroyed } from 'ngx-take-until-destroy';
import {BehaviorSubject, Subscription} from 'rxjs';
import {SessionState, SessionStateModel} from '../shared/stores/session-state/session-state';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {isNotNullOrUndefined} from 'codelyzer/util/isNotNullOrUndefined';
import {filter} from 'rxjs/operators';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit, OnDestroy{
export class AppComponent implements OnInit, OnDestroy {
title = 'security-c4po-angular';
$authState: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
@ -27,11 +29,12 @@ export class AppComponent implements OnInit, OnDestroy{
ngOnInit(): void {
// authState change handling
of(this.store.selectSnapshot(SessionState.isAuthenticated)).pipe(
this.authStateSubscription = this.store.select(SessionState).pipe(
filter(isNotNullOrUndefined),
untilDestroyed(this)
).subscribe({
next: (authState: boolean) => {
this.$authState.next(authState);
next: (state: SessionStateModel) => {
this.$authState.next(state.isAuthenticated);
},
error: (err) => console.error('auth error:', err)
});

View File

@ -6,7 +6,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {
NbLayoutModule,
NbToastrModule,
NbIconModule,
NbIconModule, NbCardModule, NbButtonModule,
} from '@nebular/theme';
import {NbEvaIconsModule} from '@nebular/eva-icons';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
@ -21,6 +21,8 @@ import {SessionState} from '../shared/stores/session-state/session-state';
import {environment} from '../environments/environment';
import {NotificationService} from '../shared/services/notification.service';
import {ThemeModule} from '@assets/@theme/theme.module';
import {HeaderModule} from './header/header.module';
import {HomeModule} from './home/home.module';
@NgModule({
declarations: [
@ -35,7 +37,9 @@ import {ThemeModule} from '@assets/@theme/theme.module';
BrowserAnimationsModule,
ThemeModule.forRoot(),
NbLayoutModule,
NbCardModule,
NbIconModule,
NbButtonModule,
NbEvaIconsModule,
NgxsModule.forRoot([SessionState], {developmentMode: !environment.production}),
HttpClientModule,
@ -46,6 +50,8 @@ import {ThemeModule} from '@assets/@theme/theme.module';
deps: [HttpClient]
}
}),
HeaderModule,
HomeModule
],
providers: [
HttpClient,

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {DashboardComponent} from './dashboard.component';
const routes: Routes = [
{
path: '',
component: DashboardComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DashboardRoutingModule { }

View File

@ -0,0 +1 @@
<p>dashboard works!</p>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
describe('DashboardComponent', () => {
let component: DashboardComponent;
let fixture: ComponentFixture<DashboardComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DashboardComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DashboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements OnInit, OnDestroy {
constructor() { }
ngOnInit(): void {
}
ngOnDestroy(): void {
}
}

View File

@ -0,0 +1,17 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {DashboardComponent} from './dashboard.component';
@NgModule({
declarations: [
DashboardComponent,
],
exports: [
DashboardComponent
],
imports: [
CommonModule
]
})
export class DashboardModule {
}

View File

@ -0,0 +1,2 @@
export {DashboardModule} from './dashboard.module';
export {DashboardRoutingModule} from './dashboard-routing.module';

View File

@ -0,0 +1,4 @@
<div class="c4po-header">
<p>header works!</p>
</div>

View File

@ -0,0 +1,9 @@
@import "../../assets/@theme/styles/_variables.scss";
.c4po-header {
height: $header-height;
.toggle-dark-mode {
text-align: right;
}
}

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {HeaderComponent} from './header.component';
@NgModule({
declarations: [
HeaderComponent
],
exports: [
HeaderComponent
],
imports: [
CommonModule
]
})
export class HeaderModule { }

View File

@ -1 +1,16 @@
<p>home works!</p>
<nb-card>
<nb-card-header>
<button (click)="onClickGetProjects()" nbButton>get Projects</button>
</nb-card-header>
<nb-card-body>
<div *ngIf="projects.getValue().length > 0, else noProjects">
{{projects.getValue() | json}}
</div>
<ng-template #noProjects>
{{'No Projects available!'}}
</ng-template>
</nb-card-body>
</nb-card>

View File

@ -1,6 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
import {NbButtonModule, NbCardModule} from '@nebular/theme';
import {HttpClientTestingModule} from '@angular/common/http/testing';
describe('HomeComponent', () => {
let component: HomeComponent;
@ -8,7 +10,14 @@ describe('HomeComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ HomeComponent ]
declarations: [
HomeComponent
],
imports: [
HttpClientTestingModule,
NbCardModule,
NbButtonModule
]
})
.compileComponents();
});

View File

@ -1,15 +1,36 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {BehaviorSubject} from 'rxjs';
import {Project} from '../../shared/models/project.model';
import {ProjectService} from '../../shared/services/project.service';
import {untilDestroyed} from 'ngx-take-until-destroy';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
export class HomeComponent implements OnInit, OnDestroy {
constructor() { }
projects: BehaviorSubject<Project[]> = new BehaviorSubject<Project[]>([]);
constructor(private projectService: ProjectService) { }
ngOnInit(): void {
}
onClickGetProjects(): void {
this.getProjects();
}
getProjects(): void {
this.projectService.getProjects()
.pipe(untilDestroyed(this))
.subscribe((projects) => {
this.projects.next(projects);
});
}
ngOnDestroy(): void {
}
}

View File

@ -1,9 +1,20 @@
import { NgModule } from '@angular/core';
import {NgModule} from '@angular/core';
import {HomeComponent} from './home.component';
import {CommonModule} from '@angular/common';
import {NbButtonModule, NbCardModule} from '@nebular/theme';
@NgModule({
declarations: [
HomeComponent
],
exports: [
HomeComponent
],
imports: [
CommonModule,
NbCardModule,
NbButtonModule
]
})
export class HomeModule { }
export class HomeModule {
}

View File

@ -51,9 +51,3 @@
</nb-card-body>
</nb-card>
</div>

View File

@ -0,0 +1 @@
$header-height: 4rem;

View File

@ -9,6 +9,7 @@
@import './layout';
@import './overrides';
@import './variables';
* {
font-family: Roboto, "Helvetica Neue", sans-serif;

View File

@ -3,7 +3,9 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
stage: 'n/a',
production: false,
apiEndpoint: 'http://localhost:8443',
};
/*

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 547 KiB

View File

@ -5,7 +5,7 @@
<title>SecurityC4POAngular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!--<link rel="icon" type="image/x-icon" href="src/favicon.ico">-->
</head>
<body id="loader-wrapper">
<app-root id="loader"></app-root>

View File

@ -24,6 +24,7 @@ export class AuthGuardService implements CanActivate {
return canAccess;
} else {
this.router.navigate(['/login']);
return false;
}
})
);

View File

@ -0,0 +1,25 @@
import { v4 as UUID } from 'uuid';
export class Project {
id: string;
client: string;
title: string;
/* Change to Date after database integration */
createdAt: string;
tester: string;
logo: string;
constructor(id: string,
client: string,
title: string,
createdAt: string,
tester?: string,
logo?: string) {
this.id = id;
this.client = client;
this.title = title;
this.createdAt = createdAt;
this.tester = tester;
this.logo = logo;
}
}

View File

@ -0,0 +1,24 @@
import { TestBed } from '@angular/core/testing';
import { ProjectService } from './project.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
describe('ProjectService', () => {
let service: ProjectService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
],
providers: []
});
service = TestBed.inject(ProjectService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import {environment} from '../../environments/environment';
import {HttpClient} from '@angular/common/http';
import {Project} from '../models/project.model';
import {Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ProjectService {
private apiBaseURL = `${environment.apiEndpoint}/v1/projects`;
constructor(private http: HttpClient) {
}
public getProjects(): Observable<Project[]> {
return this.http.get<Project[]>(`${this.apiBaseURL}`);
}
}

View File

@ -16,8 +16,10 @@ plugins {
id("io.spring.dependency-management") version "1.0.10.RELEASE"
id("com.github.spotbugs") version "4.5.0"
id("org.owasp.dependencycheck") version "6.0.0"
id("org.asciidoctor.jvm.convert") version "2.4.0"
kotlin("jvm") version "1.3.72"
kotlin("plugin.spring") version "1.3.72"
jacoco
}
group = "com.security-c4po.api"
@ -56,17 +58,33 @@ spotbugs {
val snippetsDir = file("build/generated-snippets")
dependencies {
implementation("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.11.3")
implementation("io.projectreactor.kotlin:reactor-kotlin-extensions:1.1.1")
implementation("javax.websocket:javax.websocket-api:1.1")
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
/*implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")*/
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("com.github.spotbugs:spotbugs-annotations:4.1.2")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("org.springframework.restdocs:spring-restdocs-mockmvc")
implementation("org.modelmapper:modelmapper:2.3.2")
api("org.springframework.boot:spring-boot-starter-test")
/*api("org.springframework.security:spring-security-jwt:1.0.10.RELEASE")*/
testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0")
testImplementation("io.projectreactor:reactor-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.3.1")
testImplementation("org.springframework.cloud:spring-cloud-contract-wiremock:2.1.0.RELEASE")
testImplementation("org.springframework.restdocs:spring-restdocs-webtestclient")
}
jacoco {
toolVersion = "0.8.3"
reportsDir = file("$buildDir/reports/coverage")
}
tasks.withType<Test> {
@ -81,7 +99,7 @@ tasks.withType<KotlinCompile> {
}
tasks.bootJar {
dependsOn(tasks.test, tasks.dependencyCheckAnalyze)
dependsOn(tasks.test, tasks.asciidoctor, tasks.jacocoTestReport, tasks.dependencyCheckAnalyze)
}
tasks.test {
@ -89,5 +107,35 @@ tasks.test {
}
tasks.dependencyCheckAnalyze {
dependsOn(tasks.test, tasks.asciidoctor, tasks.jacocoTestReport)
}
//Issue with Kotlin assignment of sourceDir and outputDir: https://github.com/asciidoctor/asciidoctor-gradle-plugin/issues/458
tasks.asciidoctor {
inputs.dir(snippetsDir)
setSourceDir(file("src/main/asciidoc"))
setOutputDir(file("$buildDir/asciidoc"))
sources(delegateClosureOf<PatternSet> {
include("SecurityC4PO.adoc")
})
attributes(
mapOf(
"snippets" to snippetsDir,
"source-highlighter" to "coderay",
"toc" to "left",
"toclevels" to 3,
"sectlinks" to true
)
)
dependsOn(tasks.test)
}
tasks.jacocoTestReport {
reports {
xml.isEnabled = true
csv.isEnabled = false
html.isEnabled = true
html.destination = file("$buildDir/reports/coverage")
}
}

View File

@ -0,0 +1,54 @@
{
"info": {
"_postman_id": "58021f5f-0ae9-4f64-990b-f09dcc2d3bc2",
"name": "security-c4po-api",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "project",
"item": [
{
"name": "getProjects",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8443/v1/projects",
"protocol": "http",
"host": [
"localhost"
],
"port": "8443",
"path": [
"v1",
"projects"
]
}
},
"response": []
}
]
},
{
"name": "getHealth",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "http://localhost:8443/actuator/health",
"protocol": "http",
"host": [
"localhost"
],
"port": "8443",
"path": [
"actuator",
"health"
]
}
},
"response": []
}
]
}

View File

@ -0,0 +1,50 @@
= SecurityC4PO REST API Documentation
Novatec Consulting GmbH;
:doctype: book
:source-highlighter: highlightjs
:icons: font
:toc: left
:toclevels: 4
:sectlinks:
:data-uri:
This documentation describes the REST API for the "SecurityC4PO".
The service tries to adhere as closely as possible to standard HTTP and REST conventions in its use of HTTP verbs and status codes.
== Error handling
You can generally expect 4xx for client errors and 5xx for server errors.
== Request Headers
The request and response snippets shown in this documentation are generated from real interactions.
When creating requests you must not follow the examples exactly, e.g. instead of providing the Accept Header `application/json, application/javascript, text/javascript` you can also provide only one value, typically `application/json`.
== Project
=== Get projects
To get projects, call the GET request /v1/projects
==== Request example
#include::{snippets}/getProjects/http-request.adoc[]
==== Response example
#include::{snippets}/getProjects/http-response.adoc[]
==== Response structure
#include::{snippets}/getProjects/response-fields.adoc[]
== Change History
|===
|Date |Change
|2021-02-14
|Added GET endpoint to receive Projects
|2021-02-12
|Initial version
|===

View File

@ -0,0 +1,6 @@
package com.securityc4po.api.v1
abstract class BaseEntity<T>(
var data: T
) {
}

View File

@ -0,0 +1,3 @@
package com.securityc4po.api.v1
typealias ResponseBody = Map<String, Any?>

View File

@ -1,4 +1,4 @@
package com.securityc4po.api
package com.securityc4po.api.v1
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

View File

@ -0,0 +1,19 @@
package com.securityc4po.api.v1.configuration
// Constants for SpotBugs warning suppressions
const val NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR = "NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"
const val RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"
const val BC_BAD_CAST_TO_ABSTRACT_COLLECTION = "BC_BAD_CAST_TO_ABSTRACT_COLLECTION"
const val UC_USELESS_OBJECT = "UC_USELESS_OBJECT"
const val NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE"
const val SE_BAD_FIELD = "SE_BAD_FIELD"
const val EI_EXPOSE_REP = "EI_EXPOSE_REP"
const val ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD"
const val SIC_INNER_SHOULD_BE_STATIC = "SIC_INNER_SHOULD_BE_STATIC"
const val URF_UNREAD_FIELD = "URF_UNREAD_FIELD"
// Messages for SpotBugs warning suppressions
const val MESSAGE_BAD_CAST_TO_ABSTRACT_COLLECTION = "Collection is automatically casted to abstract class."
const val MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK = "Value gets automatically initialized and checked for null"
const val MESSAGE_USELESS_OBJECT = "Objects are instantiated for code readability."
const val MESSAGE_NULL_ON_SOME_PATH = "Null is a valid value in this case."

View File

@ -0,0 +1,5 @@
package com.securityc4po.api.v1.extensions
import org.slf4j.LoggerFactory
inline fun <reified T> getLoggerFor() = LoggerFactory.getLogger(T::class.java)!!

View File

@ -0,0 +1,47 @@
package com.securityc4po.api.v1.project
import com.fasterxml.jackson.annotation.JsonFormat
import com.securityc4po.api.v1.ResponseBody
import java.time.Instant
import java.util.UUID
data class Project(
/*
* @Indexed(background = true, unique = true)
* Can be used after adding deps for mongodb
*/
val id: String = UUID.randomUUID().toString(),
val client: String,
val title: String,
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
/* Change to Instant after database integration */
val createdAt: String,
val tester: String? = null,
val logo: String? = null
)
fun Project.toProjectResponseBody(): ResponseBody {
return kotlin.collections.mapOf(
"id" to id,
"client" to client,
"title" to title,
"createdAt" to createdAt.toString(),
"tester" to tester,
"logo" to logo
)
}
data class ProjectOverview(
val projects: List<Project>
)
fun ProjectOverview.toProjectOverviewResponseBody(): ResponseBody {
return mapOf(
"projects" to projects
)
}

View File

@ -0,0 +1,28 @@
package com.securityc4po.api.v1.project
import com.securityc4po.api.v1.ResponseBody
import com.securityc4po.api.v1.configuration.BC_BAD_CAST_TO_ABSTRACT_COLLECTION
import com.securityc4po.api.v1.extensions.getLoggerFor
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/v1/projects")
@CrossOrigin(
origins = [],
allowCredentials = "false",
allowedHeaders = ["*"],
methods = [RequestMethod.GET]
)
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
class ProjectController(private val projectService: ProjectService) {
var logger = getLoggerFor<ProjectController>()
@GetMapping
fun getProjects(): List<Project> {
return projectService.getProjects()
}
}

View File

@ -0,0 +1,11 @@
package com.securityc4po.api.v1.project
import com.securityc4po.api.v1.BaseEntity
/*
* @Document(collection = "project")
* Can be used after adding deps for mongodb
*/
open class ProjectEntity(
data: Project
) : BaseEntity<Project>(data)

View File

@ -0,0 +1,40 @@
package com.securityc4po.api.v1.project
import com.securityc4po.api.v1.extensions.getLoggerFor
import org.junit.BeforeClass
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
/* Remove after database is integrated */
import java.io.File
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
@Service
class ProjectService() {
var logger = getLoggerFor<ProjectService>()
/* Remove after database is integrated */
val mapper = jacksonObjectMapper()
@BeforeClass
fun init() {
mapper.registerKotlinModule()
mapper.registerModule(JavaTimeModule())
}
/**
* Get all [Project]s
*
* @return list of [Project]
*/
fun getProjects(): List<Project> {
val jsonProjectsString: String = File("./src/main/resources/mocks/projects.json").readText(Charsets.UTF_8)
val jsonProjectList: List<Project> = mapper.readValue<List<Project>>(jsonProjectsString)
/* After database integration the return should be Flux of ProjectEntity */
return jsonProjectList;
}
}

View File

@ -0,0 +1,15 @@
package com.securityc4po.api.v1.user
data class User(
val id: String,
val username: String,
val firstName: String? = null,
val lastName: String? = null,
val email: String? = null,
val interfaceLang: String? = null
)

View File

@ -0,0 +1,11 @@
package com.securityc4po.api.v1.user
import com.securityc4po.api.v1.BaseEntity
/*
* @Document(collection = "user")
* Can be used after adding deps for mongodb
*/
open class UserEntity(
data: User
) : BaseEntity<User>(data)

View File

@ -1,4 +1,18 @@
## General Properties ##
spring.main.web-application-type=reactive
spring.main.allow-bean-definition-overriding=true
## Server Config ##
server.port=8443
server.port=8443
## Actuator Endpoints ##
management.endpoints.enabled-by-default=false
management.endpoint.health.enabled=true
management.endpoints.web.exposure.include=info, health, metrics
## Database (MONGODB) Config ##
# spring.data.mongodb.database=C4PO
# spring.data.mongodb.host=localhost
# spring.data.mongodb.port=27017
# spring.main.allow-bean-definition-overriding=true
# spring.data.mongodb.auto-index-creation=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
{
"id": "db7f247d-da43-4cbe-9fd7-c18679a2f7e7",
"username": "ttt",
"firstName": "test",
"lastName": "user",
"email": "default.user@test.de",
"interfaceLang": "en-US"
}

View File

@ -0,0 +1,9 @@
package com.securityc4po.api.v1
import org.junit.jupiter.api.TestInstance
import org.springframework.test.context.TestPropertySource
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
abstract class BaseContainerizedTest {
}

View File

@ -0,0 +1,36 @@
package com.securityc4po.api.v1
import com.securityc4po.api.v1.configuration.MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK
import com.securityc4po.api.v1.configuration.NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR
import com.securityc4po.api.v1.configuration.RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.restdocs.RestDocumentationContextProvider
import org.springframework.restdocs.RestDocumentationExtension
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.documentationConfiguration
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.web.reactive.server.WebTestClient
import java.time.Duration
@SuppressFBWarnings(NP_NONNULL_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR, RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE, MESSAGE_NOT_INITIALIZED_REDUNDANT_NULLCHECK)
@ExtendWith(value = [RestDocumentationExtension::class, SpringExtension::class])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
abstract class BaseDocumentationIntTest : BaseContainerizedTest() {
@LocalServerPort
private var port = 0
lateinit var webTestClient: WebTestClient
@BeforeEach
fun setupDocs(restDocumentation: RestDocumentationContextProvider) {
webTestClient = WebTestClient.bindToServer()
.baseUrl("http://localhost:$port")
.filter(documentationConfiguration(restDocumentation))
.responseTimeout(Duration.ofMillis(10000))
.build()
}
}

View File

@ -0,0 +1,11 @@
package com.securityc4po.api.v1
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.junit.jupiter.SpringExtension
@ExtendWith(SpringExtension::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@DirtiesContext
abstract class BaseIntTest : BaseContainerizedTest() { }

View File

@ -1,4 +1,4 @@
package com.securityc4po.api
package com.securityc4po.api.v1
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest

View File

@ -0,0 +1,109 @@
package com.securityc4po.api.v1.project
import com.github.tomakehurst.wiremock.common.Json
import com.securityc4po.api.v1.BaseDocumentationIntTest
import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock
import org.springframework.restdocs.operation.preprocess.Preprocessors
import org.springframework.restdocs.payload.JsonFieldType
import org.springframework.restdocs.payload.PayloadDocumentation
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
@AutoConfigureWireMock(port = 0)
@SuppressFBWarnings(SIC_INNER_SHOULD_BE_STATIC)
class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
/*@Autowired
lateinit var mongoTemplate: MongoTemplate*/
@BeforeEach
fun init() {
cleanUp()
persistBasicTestScenario()
}
@Nested
inner class GetProjects {
@Test
fun getProjects() {
/* Implement after the implementation of database */
/*webTestClient.get().uri("/v1/projects")
.header("")
.exchange()
.expectStatus().isOk
.expectHeader().doesNotExist("")
.expectBody().json(Json.write(getProjectsResponse()))
.consumeWith(WebTestClientRestDocumentation.document("{methodName}",
Preprocessors.preprocessRequest(Preprocessors.prettyPrint(),
Preprocessors.modifyUris().removePort(),
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 used as a tester in the Project"),
PayloadDocumentation.fieldWithPath("[].logo").type(JsonFieldType.STRING).description("The sensors contained in the Project")
)
))*/
}
val projectOne = Project(
id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9",
client = "E Corp",
title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
logo = "Insert'E_Corp.png'BASE64Encoded"
)
val projectTwo = Project(
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
client = "Allsafe",
title = "CashMyData (iOS)",
createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot",
logo = "Insert'Allsafe.png'BASE64Encoded"
)
private fun getProjectsResponse() = listOf(
projectOne.toProjectResponseBody(),
projectTwo.toProjectResponseBody()
)
}
private fun cleanUp() {
/*mongoTemplate.findAllAndRemove(Query(), Project::class.java)*/
}
private fun persistBasicTestScenario() {
// setup test data
val projectOne = Project(
id = "260aa538-0873-43fc-84de-3a09b008646d",
client = "",
title = "",
createdAt = "",
tester = "",
logo = ""
)
val projectTwo = Project(
id = "260aa538-0873-43fc-84de-3a09b008646d",
client = "",
title = "",
createdAt = "",
tester = "",
logo = ""
)
cleanUp()
/*mongoTemplate.save(ProjectEntity(projectOne))
mongoTemplate.save(ProjectEntity(projectTwo))*/
}
}

View File

@ -0,0 +1,106 @@
package com.securityc4po.api.v1.project
import com.github.tomakehurst.wiremock.common.Json
import com.securityc4po.api.v1.BaseIntTest
import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC
import com.securityc4po.api.v1.configuration.URF_UNREAD_FIELD
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.web.server.LocalServerPort
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock
import org.springframework.test.web.reactive.server.WebTestClient
import java.time.Duration
@AutoConfigureWireMock(port = 0)
@SuppressFBWarnings(SIC_INNER_SHOULD_BE_STATIC, URF_UNREAD_FIELD, "Unread field will become used after database implementation")
class ProjectControllerIntTest : BaseIntTest() {
@LocalServerPort
private var port = 0
private lateinit var webTestClient: WebTestClient
@BeforeEach
fun setupWebClient() {
webTestClient = WebTestClient.bindToServer()
.baseUrl("http://localhost:$port")
.responseTimeout(Duration.ofMillis(10000))
.build()
}
/*@Autowired
lateinit var mongoTemplate: MongoTemplate*/
@BeforeEach
fun init() {
cleanUp()
persistBasicTestScenario()
}
@Nested
inner class GetProjects {
@Test
fun `requesting projects successfully`() {
/* Implement after the implementation of database */
/*webTestClient.get().uri("/v1/projects")
.header("")
.exchange()
.expectStatus().isOk
.expectHeader().doesNotExist("")
.expectBody().json(Json.write(getProjects()))*/
}
val projectOne = Project(
id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9",
client = "E Corp",
title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
logo = "Insert'E_Corp.png'BASE64Encoded"
)
val projectTwo = Project(
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
client = "Allsafe",
title = "CashMyData (iOS)",
createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot",
logo = "Insert'Allsafe.png'BASE64Encoded"
)
private fun getProjects() = listOf(
projectOne.toProjectResponseBody(),
projectTwo.toProjectResponseBody()
)
}
private fun cleanUp() {
/*mongoTemplate.findAllAndRemove(Query(), Project::class.java)*/
}
private fun persistBasicTestScenario() {
// setup test data
val projectOne = Project(
id = "4f6567a8-76fd-487b-8602-f82d0ca4d1f9",
client = "E Corp",
title = "Some Mock API (v1.0) Scanning",
createdAt = "2021-01-10T18:05:00Z",
tester = "Novatester",
logo = "Insert'E_Corp.png'BASE64Encoded"
)
val projectTwo = Project(
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
client = "Allsafe",
title = "CashMyData (iOS)",
createdAt = "2021-01-10T18:05:00Z",
tester = "Elliot",
logo = "Insert'Allsafe.png'BASE64Encoded"
)
cleanUp()
/*mongoTemplate.save(ProjectEntity(projectOne))
mongoTemplate.save(ProjectEntity(projectTwo))*/
}
}

View File

@ -0,0 +1,27 @@
package com.securityc4po.api.v1.project
import com.nhaarman.mockitokotlin2.mock
import com.securityc4po.api.v1.configuration.SIC_INNER_SHOULD_BE_STATIC
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.slf4j.Logger
@SuppressFBWarnings(SIC_INNER_SHOULD_BE_STATIC)
class ProjectServiceTest {
private val log = mock<Logger>()
private val cut = ProjectService().apply {
this.logger = log
}
@Nested
inner class GetProjects {
@Test
fun `happy path - getProjects successfully`() {
}
}
}