feat: As a developer I want to delete projects
This commit is contained in:
parent
497ee99e92
commit
203b376ef1
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "a20516f0-5f7f-4d15-9f26-ada358993ff8",
|
"_postman_id": "1823096a-1cb2-438d-9872-73ec0ca94cfa",
|
||||||
"name": "security-c4po-api",
|
"name": "security-c4po-api",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
},
|
},
|
||||||
|
@ -10,9 +10,6 @@
|
||||||
"item": [
|
"item": [
|
||||||
{
|
{
|
||||||
"name": "getProjects",
|
"name": "getProjects",
|
||||||
"protocolProfileBehavior": {
|
|
||||||
"disableBodyPruning": true
|
|
||||||
},
|
|
||||||
"request": {
|
"request": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "oauth2",
|
"type": "oauth2",
|
||||||
|
@ -41,9 +38,38 @@
|
||||||
},
|
},
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"header": [],
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "http://localhost:8443/projects",
|
||||||
|
"protocol": "http",
|
||||||
|
"host": [
|
||||||
|
"localhost"
|
||||||
|
],
|
||||||
|
"port": "8443",
|
||||||
|
"path": [
|
||||||
|
"projects"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "saveProject",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "oauth2",
|
||||||
|
"oauth2": [
|
||||||
|
{
|
||||||
|
"key": "addTokenTo",
|
||||||
|
"value": "header",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "",
|
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j pentest\",\n \"tester\" : \"Stipe\",\n \"createdBy\" : \"10e06d7a-8dd0-4ecd-8963-056b45079c4f\"\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
|
@ -65,30 +91,11 @@
|
||||||
"response": []
|
"response": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "saveProject",
|
"name": "deleteProject",
|
||||||
"request": {
|
"request": {
|
||||||
"method": "POST",
|
"method": "GET",
|
||||||
"header": [],
|
"header": [],
|
||||||
"body": {
|
"url": null
|
||||||
"mode": "raw",
|
|
||||||
"raw": "{\n \"client\": \"Novatec\",\n \"title\": \"log4j Pentest\",\n \"tester\": \"Stipe\"\n}",
|
|
||||||
"options": {
|
|
||||||
"raw": {
|
|
||||||
"language": "json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"url": {
|
|
||||||
"raw": "http://localhost:8443/projects",
|
|
||||||
"protocol": "http",
|
|
||||||
"host": [
|
|
||||||
"localhost"
|
|
||||||
],
|
|
||||||
"port": "8443",
|
|
||||||
"path": [
|
|
||||||
"projects"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"response": []
|
"response": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,28 @@ include::{snippets}/saveProject/http-response.adoc[]
|
||||||
|
|
||||||
include::{snippets}/saveProject/response-fields.adoc[]
|
include::{snippets}/saveProject/response-fields.adoc[]
|
||||||
|
|
||||||
|
=== Delete project
|
||||||
|
|
||||||
|
To delete a project, call the DELETE request /projects/{projectId}
|
||||||
|
|
||||||
|
==== Request example
|
||||||
|
|
||||||
|
include::{snippets}/deleteProject/http-request.adoc[]
|
||||||
|
|
||||||
|
==== Response example
|
||||||
|
|
||||||
|
include::{snippets}/deleteProject/http-response.adoc[]
|
||||||
|
|
||||||
|
==== Response structure
|
||||||
|
|
||||||
|
include::{snippets}/deleteProject/response-fields.adoc[]
|
||||||
|
|
||||||
== Change History
|
== Change History
|
||||||
|
|
||||||
|===
|
|===
|
||||||
|Date |Change
|
|Date |Change
|
||||||
|
|2022-02-01
|
||||||
|
|Added DELETE endpoint to save Projects
|
||||||
|2021-12-22
|
|2021-12-22
|
||||||
|Added POST endpoint to save Projects
|
|Added POST endpoint to save Projects
|
||||||
|2021-02-14
|
|2021-02-14
|
||||||
|
|
|
@ -28,6 +28,12 @@ fun Project.toProjectResponseBody(): ResponseBody {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Project.toProjectDeleteResponseBody(): ResponseBody {
|
||||||
|
return mapOf(
|
||||||
|
"id" to id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class ProjectOverview(
|
data class ProjectOverview(
|
||||||
val projects: List<Project>
|
val projects: List<Project>
|
||||||
)
|
)
|
||||||
|
@ -44,6 +50,10 @@ data class ProjectRequestBody(
|
||||||
val tester: String? = null
|
val tester: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class ProjectDeleteRequestBody(
|
||||||
|
val id: String
|
||||||
|
)
|
||||||
|
|
||||||
fun ProjectRequestBody.toProject(): Project {
|
fun ProjectRequestBody.toProject(): Project {
|
||||||
return Project(
|
return Project(
|
||||||
id = UUID.randomUUID().toString(),
|
id = UUID.randomUUID().toString(),
|
||||||
|
@ -53,5 +63,7 @@ fun ProjectRequestBody.toProject(): Project {
|
||||||
tester = this.tester,
|
tester = this.tester,
|
||||||
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
// ToDo: Should be changed to SUB from Token after adding AUTH Header
|
||||||
createdBy = UUID.randomUUID().toString()
|
createdBy = UUID.randomUUID().toString()
|
||||||
)
|
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ import java.util.*
|
||||||
origins = [],
|
origins = [],
|
||||||
allowCredentials = "false",
|
allowCredentials = "false",
|
||||||
allowedHeaders = ["*"],
|
allowedHeaders = ["*"],
|
||||||
methods = [RequestMethod.GET, RequestMethod.POST]
|
methods = [RequestMethod.GET, RequestMethod.DELETE, RequestMethod.POST]
|
||||||
)
|
)
|
||||||
|
|
||||||
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
@SuppressFBWarnings(BC_BAD_CAST_TO_ABSTRACT_COLLECTION)
|
||||||
class ProjectController(private val projectService: ProjectService) {
|
class ProjectController(private val projectService: ProjectService) {
|
||||||
|
|
||||||
|
@ -41,4 +42,11 @@ class ProjectController(private val projectService: ProjectService) {
|
||||||
ResponseEntity.accepted().body(it.toProjectResponseBody())
|
ResponseEntity.accepted().body(it.toProjectResponseBody())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
fun deleteProject(@PathVariable(value = "id") id: String): Mono<ResponseEntity<ResponseBody>> {
|
||||||
|
return this.projectService.deleteProject(id).map{
|
||||||
|
ResponseEntity.ok().body(it.toProjectDeleteResponseBody())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.securityc4po.api.project
|
package com.securityc4po.api.project
|
||||||
|
|
||||||
|
import org.springframework.data.mongodb.repository.DeleteQuery
|
||||||
import org.springframework.data.mongodb.repository.Query
|
import org.springframework.data.mongodb.repository.Query
|
||||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
import org.springframework.data.mongodb.repository.ReactiveMongoRepository
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -10,4 +11,7 @@ interface ProjectRepository: ReactiveMongoRepository<ProjectEntity, String> {
|
||||||
|
|
||||||
@Query("{'data._id' : ?0}")
|
@Query("{'data._id' : ?0}")
|
||||||
fun findProjectById(id: String): Mono<ProjectEntity>
|
fun findProjectById(id: String): Mono<ProjectEntity>
|
||||||
|
|
||||||
|
@DeleteQuery("{'data._id' : ?0}")
|
||||||
|
fun deleteProjectById(id: String): Mono<Long>
|
||||||
}
|
}
|
|
@ -6,6 +6,8 @@ import com.securityc4po.api.extensions.getLoggerFor
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
@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)
|
||||||
|
@ -33,4 +35,14 @@ class ProjectService(private val projectRepository: ProjectRepository) {
|
||||||
logger.warn("Project could not be stored in Database. Thrown exception: ", it)
|
logger.warn("Project could not be stored in Database. Thrown exception: ", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteProject(id: String): Mono<Project> {
|
||||||
|
return projectRepository.findProjectById(id).switchIfEmpty{
|
||||||
|
logger.info("Project with id $id not found. Deletion not possible.")
|
||||||
|
Mono.empty()
|
||||||
|
}.flatMap{ projectEntity: ProjectEntity ->
|
||||||
|
val project = projectEntity.toProject()
|
||||||
|
projectRepository.deleteProjectById(id).map{project}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ abstract class BaseContainerizedTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val mongoDbContainer = KGenericContainer(ImageFromDockerfile("c4poapibasecontainerizedtest").withDockerfileFromBuilder {
|
val mongoDbContainer = KGenericContainer(ImageFromDockerfile("c4poapibasecontainerizedtest").withDockerfileFromBuilder {
|
||||||
it.from("mongo")
|
it.from("mongo:4.4.6")
|
||||||
it.env("MONGO_INITDB_ROOT_USERNAME", "root")
|
it.env("MONGO_INITDB_ROOT_USERNAME", "root")
|
||||||
it.env("MONGO_INITDB_ROOT_PASSWORD", "cjwkbencowepoc324pon2mop3mp4")
|
it.env("MONGO_INITDB_ROOT_PASSWORD", "cjwkbencowepoc324pon2mop3mp4")
|
||||||
it.env("MONGO_INITDB_DATABASE", "admin")
|
it.env("MONGO_INITDB_DATABASE", "admin")
|
||||||
|
|
|
@ -149,6 +149,46 @@ class ProjectControllerDocumentationTest : BaseDocumentationIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class DeleteProject {
|
||||||
|
@Test
|
||||||
|
fun deleteProject() {
|
||||||
|
webTestClient.delete().uri("/projects/${project.id}")
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody()
|
||||||
|
.json(Json.write(project.toProjectDeleteResponseBody()))
|
||||||
|
.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 deleted project")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val project = 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",
|
||||||
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun persistBasicTestScenario() {
|
private fun persistBasicTestScenario() {
|
||||||
// setup test data
|
// setup test data
|
||||||
val projectOne = Project(
|
val projectOne = Project(
|
||||||
|
|
|
@ -116,6 +116,36 @@ class ProjectControllerIntTest : BaseIntTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
inner class DeleteProject {
|
||||||
|
@Test
|
||||||
|
fun `deleted project successfully`() {
|
||||||
|
webTestClient.delete().uri("/projects/{id}", projectTwo.id)
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody().json(Json.write(projectTwo.toProjectDeleteResponseBody()))
|
||||||
|
}
|
||||||
|
/*@Test
|
||||||
|
fun `delete project by non-existing id`() {
|
||||||
|
webTestClient.delete().uri("/projects/{id}", "98754a47-796b-4b3f-abf9-c46c668596c5")
|
||||||
|
.header("Authorization", "Bearer $tokenAdmin")
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isNoContent
|
||||||
|
.expectHeader().valueEquals("Application-Name", "SecurityC4PO")
|
||||||
|
.expectBody().isEmpty
|
||||||
|
}*/
|
||||||
|
val projectTwo = Project(
|
||||||
|
id = "61360a47-796b-4b3f-abf9-c46c668596c5",
|
||||||
|
client = "Allsafe",
|
||||||
|
title = "CashMyData (iOS)",
|
||||||
|
createdAt = "2021-01-10T18:05:00Z",
|
||||||
|
tester = "Elliot",
|
||||||
|
createdBy = "f8aab31f-4925-4242-a6fa-f98135b4b032"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun persistBasicTestScenario() {
|
private fun persistBasicTestScenario() {
|
||||||
// setup test data
|
// setup test data
|
||||||
val projectOne = Project(
|
val projectOne = Project(
|
||||||
|
|
|
@ -2,7 +2,7 @@ version: '3.1'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
c4po-db:
|
c4po-db:
|
||||||
image: mongo:latest
|
image: mongo:4.4.6
|
||||||
container_name: c4po-db
|
container_name: c4po-db
|
||||||
volumes:
|
volumes:
|
||||||
- ../volumes/mongodb/data:/data/db
|
- ../volumes/mongodb/data:/data/db
|
||||||
|
|
Loading…
Reference in New Issue