initial backend: Dokumente, Historie, API und Persistenz (D76)
- Liquibase-Schema (`document`, `document_history`) + Rollback-scripts
- Spring Boot with JPA-repositories, entities and services
- REST-API (`/documents`, `/documents/{id}`, `/documents/{id}/history`)
- OpenAPI-specifikation for CRUD-Operationen and history
- config files (`application.yaml`, `Liquibase`, H2 im PostgreSQL-Modus)
- preps for future live-editing/delta-updates
- Exceptions for conflikt- and not-found cases (409/404)
- keeping document hostory even after `delete` for RESTORE functionality
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package com.example.editor.bdd
|
||||
|
||||
import com.example.editor.repository.DocumentHistoryRepository
|
||||
import com.example.editor.repository.DocumentRepository
|
||||
import io.cucumber.java.Before
|
||||
import io.cucumber.spring.CucumberContextConfiguration
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@CucumberContextConfiguration
|
||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
// Seit Boot 4 stellt @SpringBootTest die TestRestTemplate-Bean nicht mehr von selbst bereit
|
||||
@AutoConfigureTestRestTemplate
|
||||
class CucumberSpringConfiguration {
|
||||
|
||||
@Autowired
|
||||
private lateinit var repository: DocumentRepository
|
||||
|
||||
@Autowired
|
||||
private lateinit var historyRepository: DocumentHistoryRepository
|
||||
|
||||
@Before
|
||||
fun resetState() {
|
||||
repository.clear()
|
||||
historyRepository.clear()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.editor.bdd
|
||||
|
||||
import io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME
|
||||
import io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME
|
||||
import org.junit.platform.suite.api.ConfigurationParameter
|
||||
import org.junit.platform.suite.api.IncludeEngines
|
||||
import org.junit.platform.suite.api.SelectClasspathResource
|
||||
import org.junit.platform.suite.api.Suite
|
||||
|
||||
@Suite
|
||||
@IncludeEngines("cucumber")
|
||||
@SelectClasspathResource("features")
|
||||
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.editor.bdd")
|
||||
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty")
|
||||
class CucumberTest
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.example.editor.bdd
|
||||
|
||||
import io.cucumber.java.de.Angenommen
|
||||
import io.cucumber.java.de.Dann
|
||||
import io.cucumber.java.de.Und
|
||||
import io.cucumber.java.de.Wenn
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.resttestclient.TestRestTemplate
|
||||
import org.springframework.http.HttpEntity
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.http.ResponseEntity
|
||||
|
||||
/**
|
||||
* Behavior-Tests gegen die laufende Anwendung (RANDOM_PORT), also echtes
|
||||
* Verhalten der API inklusive Serialisierung, Statuscodes und Fehlerpfaden.
|
||||
*/
|
||||
class DocumentStepDefinitions {
|
||||
|
||||
@Autowired
|
||||
private lateinit var rest: TestRestTemplate
|
||||
|
||||
private var lastResponse: ResponseEntity<String>? = null
|
||||
private var currentDocumentId: String? = null
|
||||
|
||||
private fun jsonEntity(body: String): HttpEntity<String> {
|
||||
val headers = HttpHeaders().apply { contentType = MediaType.APPLICATION_JSON }
|
||||
return HttpEntity(body, headers)
|
||||
}
|
||||
|
||||
private fun createDocument(title: String, content: String): ResponseEntity<String> =
|
||||
rest.postForEntity(
|
||||
"/api/v1/documents",
|
||||
jsonEntity("""{"title":"$title","content":"$content"}"""),
|
||||
String::class.java,
|
||||
)
|
||||
|
||||
private fun extractId(body: String?): String {
|
||||
val match = Regex("\"id\"\\s*:\\s*\"([^\"]+)\"").find(body ?: "")
|
||||
assertNotNull(match, "Antwort enthält keine ID: $body")
|
||||
return match!!.groupValues[1]
|
||||
}
|
||||
|
||||
// ---------------- Angenommen ----------------
|
||||
|
||||
@Angenommen("es existiert ein Dokument mit dem Titel {string}")
|
||||
fun `es existiert ein Dokument`(titel: String) {
|
||||
val response = createDocument(titel, "Initialer Inhalt")
|
||||
assertEquals(201, response.statusCode.value(), "Testdatenanlage fehlgeschlagen")
|
||||
currentDocumentId = extractId(response.body)
|
||||
}
|
||||
|
||||
// ---------------- Wenn ----------------
|
||||
|
||||
@Wenn("ich ein Dokument mit dem Titel {string} und dem Inhalt {string} anlege")
|
||||
fun `ich lege ein Dokument an`(titel: String, inhalt: String) {
|
||||
lastResponse = createDocument(titel, inhalt)
|
||||
currentDocumentId = Regex("\"id\"\\s*:\\s*\"([^\"]+)\"")
|
||||
.find(lastResponse?.body ?: "")?.groupValues?.get(1)
|
||||
}
|
||||
|
||||
@Wenn("ich alle Dokumente abrufe")
|
||||
fun `ich rufe alle Dokumente ab`() {
|
||||
lastResponse = rest.getForEntity("/api/v1/documents", String::class.java)
|
||||
}
|
||||
|
||||
@Wenn("ich dieses Dokument abrufe")
|
||||
fun `ich rufe dieses Dokument ab`() {
|
||||
lastResponse = rest.getForEntity("/api/v1/documents/$currentDocumentId", String::class.java)
|
||||
}
|
||||
|
||||
@Wenn("ich ein Dokument mit einer unbekannten ID abrufe")
|
||||
fun `ich rufe ein unbekanntes Dokument ab`() {
|
||||
lastResponse = rest.getForEntity(
|
||||
"/api/v1/documents/00000000-0000-0000-0000-000000000000",
|
||||
String::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Wenn("ich den Titel dieses Dokuments auf {string} ändere")
|
||||
fun `ich aendere den Titel`(neuerTitel: String) {
|
||||
lastResponse = rest.exchange(
|
||||
"/api/v1/documents/$currentDocumentId",
|
||||
HttpMethod.PUT,
|
||||
jsonEntity("""{"title":"$neuerTitel","content":"Aktualisierter Inhalt"}"""),
|
||||
String::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Wenn("ich dieses Dokument lösche")
|
||||
fun `ich loesche dieses Dokument`() {
|
||||
lastResponse = rest.exchange(
|
||||
"/api/v1/documents/$currentDocumentId",
|
||||
HttpMethod.DELETE,
|
||||
HttpEntity.EMPTY,
|
||||
String::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------- Dann / Und ----------------
|
||||
|
||||
@Dann("erhalte ich den Status {int}")
|
||||
fun `erhalte ich den Status`(status: Int) {
|
||||
assertEquals(status, lastResponse?.statusCode?.value())
|
||||
}
|
||||
|
||||
@Und("die Antwort enthält den Titel {string}")
|
||||
fun `die Antwort enthaelt den Titel`(titel: String) {
|
||||
assertTrue(
|
||||
lastResponse?.body?.contains("\"title\":\"$titel\"") == true,
|
||||
"Erwarteter Titel '$titel' nicht in Antwort: ${lastResponse?.body}",
|
||||
)
|
||||
}
|
||||
|
||||
@Und("die Antwort enthält {int} Dokumente")
|
||||
fun `die Antwort enthaelt n Dokumente`(anzahl: Int) {
|
||||
val count = Regex("\"id\"").findAll(lastResponse?.body ?: "").count()
|
||||
assertEquals(anzahl, count, "Antwort: ${lastResponse?.body}")
|
||||
}
|
||||
|
||||
@Und("die Antwort enthält die Version {long}")
|
||||
fun `die Antwort enthaelt die Version`(version: Long) {
|
||||
assertTrue(
|
||||
lastResponse?.body?.contains("\"version\":$version") == true,
|
||||
"Erwartete Version $version nicht in Antwort: ${lastResponse?.body}",
|
||||
)
|
||||
}
|
||||
|
||||
@Und("das Dokument ist nicht mehr abrufbar")
|
||||
fun `das Dokument ist nicht mehr abrufbar`() {
|
||||
val response = rest.getForEntity("/api/v1/documents/$currentDocumentId", String::class.java)
|
||||
assertEquals(404, response.statusCode.value())
|
||||
}
|
||||
|
||||
// ---------------- Historie & Wiederherstellung ----------------
|
||||
|
||||
@Wenn("ich die Historie dieses Dokuments abrufe")
|
||||
fun `ich rufe die Historie ab`() {
|
||||
lastResponse = rest.getForEntity(
|
||||
"/api/v1/documents/$currentDocumentId/history",
|
||||
String::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Wenn("ich dieses Dokument wiederherstelle")
|
||||
fun `ich stelle dieses Dokument wieder her`() {
|
||||
lastResponse = rest.postForEntity(
|
||||
"/api/v1/documents/$currentDocumentId/restore",
|
||||
jsonEntity("{}"),
|
||||
String::class.java,
|
||||
)
|
||||
}
|
||||
|
||||
@Und("die Antwort enthält {int} Historieneinträge")
|
||||
fun `die Antwort enthaelt n Historieneintraege`(anzahl: Int) {
|
||||
val count = Regex("\"changeType\"").findAll(lastResponse?.body ?: "").count()
|
||||
assertEquals(anzahl, count, "Antwort: ${lastResponse?.body}")
|
||||
}
|
||||
|
||||
@Und("die Antwort enthält den Änderungstyp {string}")
|
||||
fun `die Antwort enthaelt den Aenderungstyp`(typ: String) {
|
||||
assertTrue(
|
||||
lastResponse?.body?.contains("\"changeType\":\"$typ\"") == true,
|
||||
"Erwarteter Änderungstyp '$typ' nicht in Antwort: ${lastResponse?.body}",
|
||||
)
|
||||
}
|
||||
|
||||
@Und("das Dokument ist wieder abrufbar")
|
||||
fun `das Dokument ist wieder abrufbar`() {
|
||||
val response = rest.getForEntity("/api/v1/documents/$currentDocumentId", String::class.java)
|
||||
assertEquals(200, response.statusCode.value())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
package com.example.editor.service
|
||||
|
||||
import com.example.editor.domain.ChangeType
|
||||
import com.example.editor.domain.Document
|
||||
import com.example.editor.domain.DocumentHistoryEntry
|
||||
import com.example.editor.repository.DocumentHistoryRepository
|
||||
import com.example.editor.repository.DocumentRepository
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertThrows
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.util.UUID
|
||||
|
||||
class DocumentServiceTest {
|
||||
|
||||
private val fixedClock: Clock =
|
||||
Clock.fixed(Instant.parse("2026-01-01T12:00:00Z"), ZoneOffset.UTC)
|
||||
|
||||
private val repository = mockk<DocumentRepository>()
|
||||
private val historyRepository = mockk<DocumentHistoryRepository>(relaxUnitFun = true)
|
||||
private val service = DocumentService(repository, historyRepository, fixedClock)
|
||||
|
||||
private fun sampleDocument(
|
||||
id: UUID = UUID.randomUUID(),
|
||||
version: Long = 1,
|
||||
) = Document(
|
||||
id = id,
|
||||
title = "Titel",
|
||||
content = "Inhalt",
|
||||
version = version,
|
||||
createdAt = OffsetDateTime.now(fixedClock),
|
||||
updatedAt = OffsetDateTime.now(fixedClock),
|
||||
)
|
||||
|
||||
private fun historyEntry(
|
||||
id: UUID,
|
||||
version: Long,
|
||||
changeType: ChangeType,
|
||||
title: String = "Titel v$version",
|
||||
content: String = "Inhalt v$version",
|
||||
) = DocumentHistoryEntry(
|
||||
documentId = id,
|
||||
version = version,
|
||||
title = title,
|
||||
content = content,
|
||||
changeType = changeType,
|
||||
timestamp = OffsetDateTime.now(fixedClock),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `create legt Dokument an und schreibt CREATED-Historieneintrag`() {
|
||||
val saved = slot<Document>()
|
||||
every { repository.save(capture(saved)) } answers { saved.captured }
|
||||
val historyEntry = slot<DocumentHistoryEntry>()
|
||||
every { historyRepository.append(capture(historyEntry)) } just runs
|
||||
|
||||
val result = service.create(title = "Notizen", content = "Hallo")
|
||||
|
||||
assertEquals(1, result.version)
|
||||
assertEquals(ChangeType.CREATED, historyEntry.captured.changeType)
|
||||
assertEquals(result.id, historyEntry.captured.documentId)
|
||||
assertEquals("Hallo", historyEntry.captured.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update inkrementiert Version und schreibt UPDATED-Historieneintrag`() {
|
||||
val doc = sampleDocument(version = 3)
|
||||
every { repository.findById(doc.id) } returns doc
|
||||
val saved = slot<Document>()
|
||||
every { repository.save(capture(saved)) } answers { saved.captured }
|
||||
val historyEntry = slot<DocumentHistoryEntry>()
|
||||
every { historyRepository.append(capture(historyEntry)) } just runs
|
||||
|
||||
val result = service.update(doc.id, title = "Neu", content = "Neuer Inhalt")
|
||||
|
||||
assertEquals(4, result.version)
|
||||
assertEquals(ChangeType.UPDATED, historyEntry.captured.changeType)
|
||||
assertEquals(4, historyEntry.captured.version)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete entfernt Dokument und schreibt DELETED-Tombstone`() {
|
||||
val doc = sampleDocument(version = 2)
|
||||
every { repository.findById(doc.id) } returns doc
|
||||
every { repository.deleteById(doc.id) } returns true
|
||||
val historyEntry = slot<DocumentHistoryEntry>()
|
||||
every { historyRepository.append(capture(historyEntry)) } just runs
|
||||
|
||||
service.delete(doc.id)
|
||||
|
||||
verify(exactly = 1) { repository.deleteById(doc.id) }
|
||||
assertEquals(ChangeType.DELETED, historyEntry.captured.changeType)
|
||||
assertEquals(3, historyEntry.captured.version)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete wirft Exception bei unbekannter ID`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { repository.findById(id) } returns null
|
||||
|
||||
assertThrows(DocumentNotFoundException::class.java) { service.delete(id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `history liefert Eintraege auch ohne existierendes Dokument`() {
|
||||
val id = UUID.randomUUID()
|
||||
val entries = listOf(
|
||||
historyEntry(id, 1, ChangeType.CREATED),
|
||||
historyEntry(id, 2, ChangeType.DELETED),
|
||||
)
|
||||
every { historyRepository.findByDocumentId(id) } returns entries
|
||||
|
||||
assertEquals(entries, service.history(id))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `history wirft Exception bei gaenzlich unbekannter ID`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { historyRepository.findByDocumentId(id) } returns emptyList()
|
||||
|
||||
assertThrows(DocumentNotFoundException::class.java) { service.history(id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restore stellt geloeschtes Dokument mit letztem Stand wieder her`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { historyRepository.findByDocumentId(id) } returns listOf(
|
||||
historyEntry(id, 1, ChangeType.CREATED),
|
||||
historyEntry(id, 2, ChangeType.UPDATED),
|
||||
historyEntry(id, 3, ChangeType.DELETED),
|
||||
)
|
||||
every { repository.findById(id) } returns null
|
||||
val saved = slot<Document>()
|
||||
every { repository.save(capture(saved)) } answers { saved.captured }
|
||||
val historyEntry = slot<DocumentHistoryEntry>()
|
||||
every { historyRepository.append(capture(historyEntry)) } just runs
|
||||
|
||||
val result = service.restore(id)
|
||||
|
||||
assertEquals(id, result.id)
|
||||
assertEquals("Titel v2", result.title)
|
||||
assertEquals("Inhalt v2", result.content)
|
||||
assertEquals(4, result.version)
|
||||
assertEquals(ChangeType.RESTORED, historyEntry.captured.changeType)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restore mit Zielversion funktioniert als Rollback fuer existierendes Dokument`() {
|
||||
val id = UUID.randomUUID()
|
||||
val existing = sampleDocument(id = id, version = 3)
|
||||
every { historyRepository.findByDocumentId(id) } returns listOf(
|
||||
historyEntry(id, 1, ChangeType.CREATED),
|
||||
historyEntry(id, 2, ChangeType.UPDATED),
|
||||
historyEntry(id, 3, ChangeType.UPDATED),
|
||||
)
|
||||
every { repository.findById(id) } returns existing
|
||||
val saved = slot<Document>()
|
||||
every { repository.save(capture(saved)) } answers { saved.captured }
|
||||
every { historyRepository.append(any()) } just runs
|
||||
|
||||
val result = service.restore(id, targetVersion = 1)
|
||||
|
||||
assertEquals("Titel v1", result.title)
|
||||
assertEquals(4, result.version)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restore ohne Zielversion wirft Konflikt wenn Dokument noch existiert`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { historyRepository.findByDocumentId(id) } returns listOf(
|
||||
historyEntry(id, 1, ChangeType.CREATED),
|
||||
)
|
||||
every { repository.findById(id) } returns sampleDocument(id = id)
|
||||
|
||||
assertThrows(DocumentConflictException::class.java) { service.restore(id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `restore wirft Exception bei unbekannter ID`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { historyRepository.findByDocumentId(id) } returns emptyList()
|
||||
|
||||
assertThrows(DocumentNotFoundException::class.java) { service.restore(id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findById wirft Exception bei unbekannter ID`() {
|
||||
val id = UUID.randomUUID()
|
||||
every { repository.findById(id) } returns null
|
||||
|
||||
assertThrows(DocumentNotFoundException::class.java) { service.findById(id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `findAll delegiert an das Repository`() {
|
||||
val docs = listOf(sampleDocument(), sampleDocument())
|
||||
every { repository.findAll() } returns docs
|
||||
|
||||
assertEquals(docs, service.findAll())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:editor-test;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH;DB_CLOSE_DELAY=-1
|
||||
username: sa
|
||||
password: ""
|
||||
driver-class-name: org.h2.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
open-in-view: false
|
||||
liquibase:
|
||||
change-log: classpath:db/changelog/db.changelog-master.sql
|
||||
@@ -0,0 +1,69 @@
|
||||
# language: de
|
||||
Funktionalität: Dokumente verwalten
|
||||
Als Nutzer der API
|
||||
möchte ich Dokumente anlegen, abrufen, ändern und löschen können,
|
||||
damit das Backend die Grundlage für den Editor bildet.
|
||||
|
||||
Szenario: Ein neues Dokument anlegen
|
||||
Wenn ich ein Dokument mit dem Titel "Notizen" und dem Inhalt "Hallo Welt" anlege
|
||||
Dann erhalte ich den Status 201
|
||||
Und die Antwort enthält den Titel "Notizen"
|
||||
Und die Antwort enthält die Version 1
|
||||
|
||||
Szenario: Alle Dokumente auflisten
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Erstes"
|
||||
Und es existiert ein Dokument mit dem Titel "Zweites"
|
||||
Wenn ich alle Dokumente abrufe
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält 2 Dokumente
|
||||
|
||||
Szenario: Ein einzelnes Dokument abrufen
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Protokoll"
|
||||
Wenn ich dieses Dokument abrufe
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält den Titel "Protokoll"
|
||||
|
||||
Szenario: Ein Dokument aktualisieren erhöht die Version
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Entwurf"
|
||||
Wenn ich den Titel dieses Dokuments auf "Final" ändere
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält den Titel "Final"
|
||||
Und die Antwort enthält die Version 2
|
||||
|
||||
Szenario: Ein Dokument löschen
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Veraltet"
|
||||
Wenn ich dieses Dokument lösche
|
||||
Dann erhalte ich den Status 204
|
||||
Und das Dokument ist nicht mehr abrufbar
|
||||
|
||||
Szenario: Ein unbekanntes Dokument abrufen
|
||||
Wenn ich ein Dokument mit einer unbekannten ID abrufe
|
||||
Dann erhalte ich den Status 404
|
||||
|
||||
Szenario: Die Historie protokolliert alle Änderungen
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Bericht"
|
||||
Wenn ich den Titel dieses Dokuments auf "Bericht v2" ändere
|
||||
Und ich die Historie dieses Dokuments abrufe
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält 2 Historieneinträge
|
||||
Und die Antwort enthält den Änderungstyp "CREATED"
|
||||
Und die Antwort enthält den Änderungstyp "UPDATED"
|
||||
|
||||
Szenario: Die Historie überlebt das Löschen eines Dokuments
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Wichtig"
|
||||
Wenn ich dieses Dokument lösche
|
||||
Und ich die Historie dieses Dokuments abrufe
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält den Änderungstyp "DELETED"
|
||||
|
||||
Szenario: Ein gelöschtes Dokument wiederherstellen
|
||||
Angenommen es existiert ein Dokument mit dem Titel "Vertrag"
|
||||
Wenn ich dieses Dokument lösche
|
||||
Und ich dieses Dokument wiederherstelle
|
||||
Dann erhalte ich den Status 200
|
||||
Und die Antwort enthält den Titel "Vertrag"
|
||||
Und das Dokument ist wieder abrufbar
|
||||
|
||||
Szenario: Wiederherstellen ohne Historie schlägt fehl
|
||||
Wenn ich ein Dokument mit einer unbekannten ID abrufe
|
||||
Dann erhalte ich den Status 404
|
||||
Reference in New Issue
Block a user