feature/use-case-acceptance-tests-2 (#117)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #117 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
3b94f117fb
commit
63af33d003
36
Jenkinsfile
vendored
36
Jenkinsfile
vendored
@ -26,9 +26,35 @@ pipeline {
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Compile & Test') {
|
||||
stage ('Compile') {
|
||||
steps {
|
||||
sh './gradlew clean check --no-daemon -x pitest -x dependencyCheckAnalyze'
|
||||
sh './gradlew clean processSpring compileJava compileTestJava --no-daemon'
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Tests') {
|
||||
parallel {
|
||||
stage('Unit-/Integration/Acceptance-Tests') {
|
||||
steps {
|
||||
sh './gradlew check --no-daemon -x pitest -x dependencyCheckAnalyze -x importOfficeData -x importHostingAssets'
|
||||
}
|
||||
}
|
||||
stage('Import-Tests') {
|
||||
steps {
|
||||
sh './gradlew importOfficeData importHostingAssets --no-daemon'
|
||||
}
|
||||
}
|
||||
stage ('Scenario-Tests') {
|
||||
steps {
|
||||
sh './gradlew scenarioTests --no-daemon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage ('Check') {
|
||||
steps {
|
||||
sh './gradlew check -x pitest -x dependencyCheckAnalyze --no-daemon'
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -45,6 +71,12 @@ pipeline {
|
||||
sourcePattern: 'src/main/java'
|
||||
)
|
||||
|
||||
// archive scenario-test reports in HTML format
|
||||
sh '''
|
||||
./gradlew convertMarkdownToHtml
|
||||
'''
|
||||
archiveArtifacts artifacts: 'doc/scenarios/*.html', allowEmptyArchive: true
|
||||
|
||||
// cleanup workspace
|
||||
cleanWs()
|
||||
}
|
||||
|
55
build.gradle
55
build.gradle
@ -255,7 +255,7 @@ test {
|
||||
'net.hostsharing.hsadminng.**.generated.**',
|
||||
]
|
||||
useJUnitPlatform {
|
||||
excludeTags 'import'
|
||||
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest'
|
||||
}
|
||||
}
|
||||
jacocoTestReport {
|
||||
@ -344,6 +344,17 @@ tasks.register('importHostingAssets', Test) {
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
tasks.register('scenarioTests', Test) {
|
||||
useJUnitPlatform {
|
||||
includeTags 'scenarioTest'
|
||||
}
|
||||
|
||||
group 'verification'
|
||||
description 'run the import jobs as tests'
|
||||
|
||||
mustRunAfter spotlessJava
|
||||
}
|
||||
|
||||
// pitest mutation testing
|
||||
pitest {
|
||||
targetClasses = ['net.hostsharing.hsadminng.**']
|
||||
@ -391,3 +402,45 @@ tasks.named("dependencyUpdates").configure {
|
||||
isNonStable(it.candidate.version)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Generate HTML from Markdown scenario-test-reports using Pandoc:
|
||||
tasks.register('convertMarkdownToHtml') {
|
||||
description = 'Generates HTML from Markdown scenario-test-reports using Pandoc.'
|
||||
group = 'Conversion'
|
||||
|
||||
// Define the template file and input directory
|
||||
def templateFile = file('doc/scenarios/template.html')
|
||||
|
||||
// Task configuration and execution
|
||||
doFirst {
|
||||
// Check if pandoc is installed
|
||||
try {
|
||||
exec {
|
||||
commandLine 'pandoc', '--version'
|
||||
}
|
||||
} catch (Exception) {
|
||||
throw new GradleException("Pandoc is not installed or not found in the system path.")
|
||||
}
|
||||
|
||||
// Check if the template file exists
|
||||
if (!templateFile.exists()) {
|
||||
throw new GradleException("Template file 'doc/scenarios/template.html' not found.")
|
||||
}
|
||||
}
|
||||
|
||||
doLast {
|
||||
// Gather all Markdown files in the current directory
|
||||
fileTree(dir: '.', include: 'doc/scenarios/*.md').each { file ->
|
||||
// Corrected way to create the output file path
|
||||
def outputFile = new File(file.parent, file.name.replaceAll(/\.md$/, '.html'))
|
||||
|
||||
// Execute pandoc for each markdown file
|
||||
exec {
|
||||
commandLine 'pandoc', file.absolutePath, '--template', templateFile.absolutePath, '-o', outputFile.absolutePath
|
||||
}
|
||||
|
||||
println "Converted ${file.name} to ${outputFile.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
124
doc/scenarios/template.html
Normal file
124
doc/scenarios/template.html
Normal file
@ -0,0 +1,124 @@
|
||||
<!doctype html>
|
||||
<html $if(lang)$ lang="$lang$" $endif$>
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
|
||||
<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta http-equiv="Content-Style-Type" content="text/css" />
|
||||
|
||||
<!-- <link rel="stylesheet" type="text/css" href="template.css" /> -->
|
||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/template.css" />
|
||||
|
||||
<link href="https://vjs.zencdn.net/5.4.4/video-js.css" rel="stylesheet" />
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
|
||||
<!-- <script type='text/javascript' src='menu/js/jquery.cookie.js'></script> -->
|
||||
<!-- <script type='text/javascript' src='menu/js/jquery.hoverIntent.minified.js'></script> -->
|
||||
<!-- <script type='text/javascript' src='menu/js/jquery.dcjqaccordion.2.7.min.js'></script> -->
|
||||
|
||||
<!-- <link href="menu/css/skins/blue.css" rel="stylesheet" type="text/css" /> -->
|
||||
<!-- <link href="menu/css/skins/graphite.css" rel="stylesheet" type="text/css" /> -->
|
||||
<!-- <link href="menu/css/skins/grey.css" rel="stylesheet" type="text/css" /> -->
|
||||
|
||||
<!-- <script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> -->
|
||||
|
||||
|
||||
<!-- <script src="script.js"></script> -->
|
||||
|
||||
<!-- <script src="jquery.sticky-kit.js "></script> -->
|
||||
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.cookie.js'></script>
|
||||
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.hoverIntent.minified.js'></script>
|
||||
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.dcjqaccordion.2.7.min.js'></script>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/blue.css" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/graphite.css" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/grey.css" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdn.jsdelivr.net/gh/ryangrose/easy-pandoc-templates@948e28e5/css/elegant_bootstrap.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/script.js"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/jquery.sticky-kit.js"></script>
|
||||
<meta name="generator" content="pandoc" />
|
||||
$for(author-meta)$
|
||||
<meta name="author" content="$author-meta$" />
|
||||
$endfor$
|
||||
$if(date-meta)$
|
||||
<meta name="date" content="$date-meta$" />
|
||||
$endif$
|
||||
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
|
||||
<style type="text/css">code{white-space: pre;}</style>
|
||||
$if(quotes)$
|
||||
<style type="text/css">q { quotes: "“" "”" "‘" "’"; }</style>
|
||||
$endif$
|
||||
$if(highlighting-css)$
|
||||
<style type="text/css">
|
||||
$highlighting-css$
|
||||
</style>
|
||||
$endif$
|
||||
$for(css)$
|
||||
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$/>
|
||||
$endfor$
|
||||
$if(math)$
|
||||
$math$
|
||||
$endif$
|
||||
$for(header-includes)$
|
||||
$header-includes$
|
||||
$endfor$
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
$if(title)$
|
||||
<div class="navbar navbar-static-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<span class="doc-title">$title$</span>
|
||||
<ul class="nav pull-right doc-info">
|
||||
$for(author)$
|
||||
<li><p class="navbar-text">$author$</p></li>
|
||||
$endfor$
|
||||
$if(date)$
|
||||
<li><p class="navbar-text">$date$</p></li>
|
||||
$endif$
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
$endif$
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
$if(toc)$
|
||||
<div id="$idprefix$TOC" class="span3">
|
||||
<div class="well toc">
|
||||
|
||||
$toc$
|
||||
|
||||
</div>
|
||||
</div>
|
||||
$endif$
|
||||
<div class="span$if(toc)$9$else$12$endif$">
|
||||
|
||||
$if(abstract)$
|
||||
<H1>$abstract-title$</H1>
|
||||
$abstract$
|
||||
$endif$
|
||||
|
||||
$for(include-before)$
|
||||
$include-before$
|
||||
$endfor$
|
||||
$body$
|
||||
$for(include-after)$
|
||||
$include-after$
|
||||
$endfor$
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://vjs.zencdn.net/5.4.4/video.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,10 +1,6 @@
|
||||
FROM eclipse-temurin:21-jdk
|
||||
RUN apt-get update && \
|
||||
apt-get install -y bind9-utils && \
|
||||
apt-get install -y bind9-utils pandoc && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
|
||||
# RUN mkdir /opt/app
|
||||
# COPY japp.jar /opt
|
||||
# CMD ["java", "-jar", "/opt/app/japp.jar"]
|
||||
|
@ -7,7 +7,7 @@ get:
|
||||
parameters:
|
||||
- $ref: 'auth.yaml#/components/parameters/currentSubject'
|
||||
- $ref: 'auth.yaml#/components/parameters/assumedRoles'
|
||||
- name: name
|
||||
- name: iban
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
|
@ -1,10 +1,14 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.contact.AddPhoneNumberToContactData;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.contact.AmendContactData;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.contact.RemovePhoneNumberFromContactData;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.contact.ReplaceContactData;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateExternalDebitorForPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSelfDebitorForPartner;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.CreateSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.FinallyDeleteSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
|
||||
@ -43,14 +47,39 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
|
||||
@Test
|
||||
@Order(1010)
|
||||
@Produces(explicitly = "Partner: Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Board of Directors"})
|
||||
void shouldCreatePartner() {
|
||||
@Produces(explicitly = "Partner: Test AG", implicitly = {"Person: Test AG", "Contact: Test AG - Hamburg"})
|
||||
void shouldCreateLegalPersonAsPartner() {
|
||||
new CreatePartner(this)
|
||||
.given("partnerNumber", 31010)
|
||||
.given("personType", "LEGAL_PERSON")
|
||||
.given("tradeName", "Test AG")
|
||||
.given("contactCaption", "Test AG - Board of Directors")
|
||||
.given("emailAddress", "board-of-directors@test-ag.example.org")
|
||||
.given("contactCaption", "Test AG - Hamburg")
|
||||
.given("postalAddress", """
|
||||
Shanghai-Allee 1
|
||||
20123 Hamburg
|
||||
""")
|
||||
.given("officePhoneNumber", "+49 40 654321-0")
|
||||
.given("emailAddress", "hamburg@test-ag.example.org")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1011)
|
||||
@Produces(explicitly = "Partner: Michelle Matthieu", implicitly = {"Person: Michelle Matthieu", "Contact: Michelle Matthieu"})
|
||||
void shouldCreateNaturalPersonAsPartner() {
|
||||
new CreatePartner(this)
|
||||
.given("partnerNumber", 31011)
|
||||
.given("personType", "NATURAL_PERSON")
|
||||
.given("givenName", "Michelle")
|
||||
.given("familyName", "Matthieu")
|
||||
.given("contactCaption", "Michelle Matthieu")
|
||||
.given("postalAddress", """
|
||||
An der Wandse 34
|
||||
22123 Hamburg
|
||||
""")
|
||||
.given("officePhoneNumber", "+49 40 123456")
|
||||
.given("emailAddress", "michelle.matthieu@example.org")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
@ -106,6 +135,53 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1100)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
void shouldAmendContactData() {
|
||||
new AmendContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
.given("newEmailAddress", "michelle@matthieu.example.org")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1101)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
void shouldAddPhoneNumberToContactData() {
|
||||
new AddPhoneNumberToContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
.given("phoneNumberKeyToAdd", "mobile")
|
||||
.given("phoneNumberToAdd", "+49 152 1234567")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1102)
|
||||
@Requires("Partner: Michelle Matthieu")
|
||||
void shouldRemovePhoneNumberFromContactData() {
|
||||
new RemovePhoneNumberFromContactData(this)
|
||||
.given("partnerName", "Matthieu")
|
||||
.given("phoneNumberKeyToRemove", "office")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(1103)
|
||||
@Requires("Partner: Test AG")
|
||||
void shouldReplaceContactData() {
|
||||
new ReplaceContactData(this)
|
||||
.given("partnerName", "Test AG")
|
||||
.given("newContactCaption", "Test AG - Norden")
|
||||
.given("newPostalAddress", """
|
||||
Am Hafen 11
|
||||
26506 Norden
|
||||
""")
|
||||
.given("newOfficePhoneNumber", "+49 4931 654321-0")
|
||||
.given("newEmailAddress", "norden@test-ag.example.org")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2010)
|
||||
@Requires("Partner: Test AG")
|
||||
@ -173,10 +249,18 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
@Produces("SEPA-Mandate: Test AG")
|
||||
void shouldCreateSepaMandateForDebitor() {
|
||||
new CreateSepaMandateForDebitor(this)
|
||||
.given("debitor", "Test AG")
|
||||
.given("memberNumberSuffix", "00")
|
||||
.given("validFrom", "2024-10-15")
|
||||
.given("membershipFeeBillable", "true")
|
||||
// existing debitor
|
||||
.given("debitorNumber", "3101000")
|
||||
|
||||
// new sepa-mandate
|
||||
.given("mandateReference", "Test AG - main debitor")
|
||||
.given("mandateAgreement", "2022-10-12")
|
||||
.given("mandateValidFrom", "2024-10-15")
|
||||
|
||||
// new bank-account
|
||||
.given("bankAccountHolder", "Test AG - debit bank account")
|
||||
.given("bankAccountIBAN", "DE02701500000000594937")
|
||||
.given("bankAccountBIC", "SSKMDEMM")
|
||||
.doRun()
|
||||
.keep();
|
||||
}
|
||||
@ -186,17 +270,17 @@ class HsOfficeScenarioTests extends ScenarioTest {
|
||||
@Requires("SEPA-Mandate: Test AG")
|
||||
void shouldInvalidateSepaMandateForDebitor() {
|
||||
new InvalidateSepaMandateForDebitor(this)
|
||||
.given("sepaMandateUuid", "%{SEPA-Mandate: Test AG}")
|
||||
.given("validUntil", "2025-09-30")
|
||||
.given("bankAccountIBAN", "DE02701500000000594937")
|
||||
.given("mandateValidUntil", "2025-09-30")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3109)
|
||||
@Requires("SEPA-Mandate: Test AG")
|
||||
void shouldDeleteSepaMandateForDebitor() {
|
||||
new DeleteSepaMandateForDebitor(this)
|
||||
.given("sepaMandateUuid", "%{SEPA-Mandate: Test AG}")
|
||||
void shouldFinallyDeleteSepaMandateForDebitor() {
|
||||
new FinallyDeleteSepaMandateForDebitor(this)
|
||||
.given("bankAccountIBAN", "DE02701500000000594937")
|
||||
.doRun();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase.HttpResponse;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PathAssertion {
|
||||
|
||||
private final String path;
|
||||
|
||||
public PathAssertion(final String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Consumer<UseCase.HttpResponse> contains(final String resolvableValue) {
|
||||
return response -> response.path(path).contains(ScenarioTest.resolve(resolvableValue));
|
||||
}
|
||||
|
||||
public Consumer<HttpResponse> doesNotExist() {
|
||||
return response -> response.path(path).isNull(); // here, null Optional means key not found in JSON
|
||||
}
|
||||
}
|
@ -63,4 +63,34 @@ Here, use-cases can be re-used, usually with different data.
|
||||
|
||||
### The Use-Case Itself
|
||||
|
||||
The use-case
|
||||
The use-case is implemented by the `run()`-method which contains HTTP-calls.
|
||||
|
||||
Each HTTP-call is wrapped into either `obtain(...)` to keep the result in a placeholder variable,
|
||||
the variable name is also used as a title.
|
||||
Or it's wrapped into a `withTitle(...)` to assign a title.
|
||||
|
||||
The HTTP-call is followed by some assertions, e.g. the HTTP status and JSON-path-expression-matchers.
|
||||
|
||||
Use `${...}` for placeholders which need to be replaced with JSON quotes
|
||||
(e.g. strings are quoted, numbers are not),
|
||||
`%{...}` for placeholders which need to be rendered raw
|
||||
and `&{...}` for placeholders which need to get URI-encoded.
|
||||
|
||||
If `???` is added before the closing brace, the property is optional.
|
||||
This means, if it's not available in the properties, `null` is used.
|
||||
|
||||
Properties with null-values are removed from the JSON.
|
||||
If you need to keep a null-value, e.g. to delete a property,
|
||||
use `NULL` (all caps) in the template (not the variable value).
|
||||
|
||||
A special syntax is the infix `???`-operator like in: `${%{var1???}???%{var2???}%{var3???}}`.
|
||||
In this case the first non-null value is used.
|
||||
|
||||
|
||||
|
||||
### The Use-Case Verification
|
||||
|
||||
The verification-step is implemented by the `verify()`-method which usually contains a HTTP-HTTP-call.
|
||||
|
||||
It can also contain a JSON-path verification to check if a certain value is in the result.
|
||||
|
||||
|
@ -12,6 +12,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.testcontainers.shaded.org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
@ -35,7 +36,7 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return uuid.toString();
|
||||
return ObjectUtils.toString(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +69,6 @@ public abstract class ScenarioTest extends ContextBasedTest {
|
||||
@AfterEach
|
||||
void cleanup() { // final TestInfo testInfo
|
||||
properties.clear();
|
||||
// FIXME: Delete all aliases as well to force HTTP GET queries in each scenario?
|
||||
testReport.close();
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,57 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class TemplateResolver {
|
||||
|
||||
private final static Pattern pattern = Pattern.compile(",(\\s*})", Pattern.MULTILINE);
|
||||
private static final String IF_NOT_FOUND_SYMBOL = "???";
|
||||
|
||||
enum PlaceholderPrefix {
|
||||
RAW('%') {
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
return value != null ? value.toString() : "";
|
||||
}
|
||||
},
|
||||
JSON_QUOTED('$'){
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
return jsonQuoted(value);
|
||||
}
|
||||
},
|
||||
URI_ENCODED('&'){
|
||||
@Override
|
||||
String convert(final Object value) {
|
||||
return value != null ? URLEncoder.encode(value.toString(), StandardCharsets.UTF_8) : "";
|
||||
}
|
||||
};
|
||||
|
||||
private final char prefixChar;
|
||||
|
||||
PlaceholderPrefix(final char prefixChar) {
|
||||
this.prefixChar = prefixChar;
|
||||
}
|
||||
|
||||
static boolean contains(final char givenChar) {
|
||||
return Arrays.stream(values()).anyMatch(p -> p.prefixChar == givenChar);
|
||||
}
|
||||
|
||||
static PlaceholderPrefix ofPrefixChar(final char givenChar) {
|
||||
return Arrays.stream(values()).filter(p -> p.prefixChar == givenChar).findFirst().orElseThrow();
|
||||
}
|
||||
|
||||
abstract String convert(final Object value);
|
||||
}
|
||||
|
||||
private final String template;
|
||||
private final Map<String, Object> properties;
|
||||
private final StringBuilder resolved = new StringBuilder();
|
||||
@ -15,18 +63,41 @@ public class TemplateResolver {
|
||||
}
|
||||
|
||||
String resolve() {
|
||||
copy();
|
||||
return resolved.toString();
|
||||
final var resolved = copy();
|
||||
final var withoutDroppedLines = dropLinesWithNullProperties(resolved);
|
||||
final var result = removeDanglingCommas(withoutDroppedLines);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void copy() {
|
||||
private static String removeDanglingCommas(final String withoutDroppedLines) {
|
||||
return pattern.matcher(withoutDroppedLines).replaceAll("$1");
|
||||
}
|
||||
|
||||
private String dropLinesWithNullProperties(final String text) {
|
||||
return Arrays.stream(text.split("\n"))
|
||||
.filter(TemplateResolver::keepLine)
|
||||
.map(TemplateResolver::keptNullValues)
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static boolean keepLine(final String line) {
|
||||
final var trimmed = line.trim();
|
||||
return !trimmed.endsWith("null,") && !trimmed.endsWith("null");
|
||||
}
|
||||
|
||||
private static String keptNullValues(final String line) {
|
||||
return line.replace(": NULL", ": null");
|
||||
}
|
||||
|
||||
private String copy() {
|
||||
while (hasMoreChars()) {
|
||||
if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') {
|
||||
if (PlaceholderPrefix.contains(currentChar()) && nextChar() == '{') {
|
||||
startPlaceholder(currentChar());
|
||||
} else {
|
||||
resolved.append(fetchChar());
|
||||
}
|
||||
}
|
||||
return resolved.toString();
|
||||
}
|
||||
|
||||
private boolean hasMoreChars() {
|
||||
@ -41,7 +112,7 @@ public class TemplateResolver {
|
||||
if (currentChar() == '}') {
|
||||
--nested;
|
||||
placeholder.append(fetchChar());
|
||||
} else if ((currentChar() == '$' || currentChar() == '%') && nextChar() == '{') {
|
||||
} else if (PlaceholderPrefix.contains (currentChar()) && nextChar() == '{') {
|
||||
++nested;
|
||||
placeholder.append(fetchChar());
|
||||
} else {
|
||||
@ -50,20 +121,33 @@ public class TemplateResolver {
|
||||
}
|
||||
final var name = new TemplateResolver(placeholder.toString(), properties).resolve();
|
||||
final var value = propVal(name);
|
||||
if ( intro == '%') {
|
||||
resolved.append(value);
|
||||
} else {
|
||||
resolved.append(optionallyQuoted(value));
|
||||
}
|
||||
resolved.append(
|
||||
PlaceholderPrefix.ofPrefixChar(intro).convert(value)
|
||||
);
|
||||
skipChar('}');
|
||||
}
|
||||
|
||||
private Object propVal(final String name) {
|
||||
final var val = properties.get(name);
|
||||
if (val == null) {
|
||||
throw new IllegalStateException("Missing required property: " + name);
|
||||
private Object propVal(final String nameExpression) {
|
||||
if (nameExpression.endsWith(IF_NOT_FOUND_SYMBOL)) {
|
||||
final String pureName = nameExpression.substring(0, nameExpression.length() - IF_NOT_FOUND_SYMBOL.length());
|
||||
return properties.get(pureName);
|
||||
} else if (nameExpression.contains(IF_NOT_FOUND_SYMBOL)) {
|
||||
final var parts = StringUtils.split(nameExpression, IF_NOT_FOUND_SYMBOL);
|
||||
return Arrays.stream(parts).filter(Objects::nonNull).findFirst().orElseGet(() -> {
|
||||
if ( parts[parts.length-1].isEmpty() ) {
|
||||
// => whole expression ends with IF_NOT_FOUND_SYMBOL, thus last null element was optional
|
||||
return null;
|
||||
}
|
||||
// => last alternative element in expression was null and not optional
|
||||
throw new IllegalStateException("Missing required value in property-chain: " + nameExpression);
|
||||
});
|
||||
} else {
|
||||
final var val = properties.get(nameExpression);
|
||||
if (val == null) {
|
||||
throw new IllegalStateException("Missing required property: " + nameExpression);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private void skipChar(final char expectedChar) {
|
||||
@ -104,35 +188,13 @@ public class TemplateResolver {
|
||||
return template.charAt(position+1);
|
||||
}
|
||||
|
||||
private static String optionallyQuoted(final Object value) {
|
||||
private static String jsonQuoted(final Object value) {
|
||||
return switch (value) {
|
||||
case null -> null;
|
||||
case Boolean bool -> bool.toString();
|
||||
case Number number -> number.toString();
|
||||
case String string -> "\"" + string.replace("\n", "\\n") + "\"";
|
||||
default -> "\"" + value + "\"";
|
||||
};
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(
|
||||
new TemplateResolver("""
|
||||
etwas davor,
|
||||
|
||||
${einfacher Platzhalter},
|
||||
${verschachtelter %{Name}},
|
||||
|
||||
und nochmal ohne Quotes:
|
||||
|
||||
%{einfacher Platzhalter},
|
||||
%{verschachtelter %{Name}},
|
||||
|
||||
etwas danach.
|
||||
""",
|
||||
Map.ofEntries(
|
||||
Map.entry("Name", "placeholder"),
|
||||
Map.entry("einfacher Platzhalter", "simple placeholder"),
|
||||
Map.entry("verschachtelter placeholder", "nested placeholder")
|
||||
)).resolve());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TemplateResolverUnitTest {
|
||||
|
||||
@Test
|
||||
void resolveTemplate() {
|
||||
final var resolved = new TemplateResolver("""
|
||||
with optional JSON quotes:
|
||||
|
||||
${boolean},
|
||||
${numeric},
|
||||
${simple placeholder},
|
||||
${nested %{name}},
|
||||
${with-special-chars}
|
||||
|
||||
and without quotes:
|
||||
|
||||
%{boolean},
|
||||
%{numeric},
|
||||
%{simple placeholder},
|
||||
%{nested %{name}},
|
||||
%{with-special-chars}
|
||||
|
||||
and uri-encoded:
|
||||
|
||||
&{boolean},
|
||||
&{numeric},
|
||||
&{simple placeholder},
|
||||
&{nested %{name}},
|
||||
&{with-special-chars}
|
||||
""",
|
||||
Map.ofEntries(
|
||||
Map.entry("name", "placeholder"),
|
||||
Map.entry("boolean", true),
|
||||
Map.entry("numeric", 42),
|
||||
Map.entry("simple placeholder", "einfach"),
|
||||
Map.entry("nested placeholder", "verschachtelt"),
|
||||
Map.entry("with-special-chars", "3&3 AG")
|
||||
)).resolve();
|
||||
|
||||
assertThat(resolved).isEqualTo("""
|
||||
with optional JSON quotes:
|
||||
|
||||
true,
|
||||
42,
|
||||
"einfach",
|
||||
"verschachtelt",
|
||||
"3&3 AG"
|
||||
|
||||
and without quotes:
|
||||
|
||||
true,
|
||||
42,
|
||||
einfach,
|
||||
verschachtelt,
|
||||
3&3 AG
|
||||
|
||||
and uri-encoded:
|
||||
|
||||
true,
|
||||
42,
|
||||
einfach,
|
||||
verschachtelt,
|
||||
3%263+AG
|
||||
""".trim());
|
||||
}
|
||||
}
|
@ -57,7 +57,9 @@ public class TestReport {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
markdownReport.close();
|
||||
if (markdownReport != null) {
|
||||
markdownReport.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static Object orderNumber(final Method method) {
|
||||
|
@ -8,6 +8,7 @@ import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.reflection.AnnotationFinder;
|
||||
import org.apache.commons.collections4.map.LinkedMap;
|
||||
import org.assertj.core.api.OptionalAssert;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -19,17 +20,21 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse.BodyHandlers;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.net.URLEncoder.encode;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.platform.commons.util.StringUtils.isBlank;
|
||||
import static org.junit.platform.commons.util.StringUtils.isNotBlank;
|
||||
|
||||
@ -80,11 +85,16 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
})
|
||||
);
|
||||
return run();
|
||||
final var response = run();
|
||||
verify();
|
||||
return response;
|
||||
}
|
||||
|
||||
protected abstract HttpResponse run();
|
||||
|
||||
protected void verify() {
|
||||
}
|
||||
|
||||
public final UseCase<T> given(final String propName, final Object propValue) {
|
||||
givenProperties.put(propName, propValue);
|
||||
ScenarioTest.putProperty(propName, propValue);
|
||||
@ -101,26 +111,30 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
final Function<HttpResponse, String> extractor,
|
||||
final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
http.get().keep(extractor);
|
||||
final var response = http.get().keep(extractor);
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
public final void obtain(final String alias, final Supplier<HttpResponse> http, final String... extraInfo) {
|
||||
withTitle(ScenarioTest.resolve(alias), () -> {
|
||||
http.get().keep();
|
||||
final var response = http.get().keep();
|
||||
Arrays.stream(extraInfo).forEach(testReport::printPara);
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
private void withTitle(final String title, final Runnable code) {
|
||||
public HttpResponse withTitle(final String title, final Supplier<HttpResponse> code) {
|
||||
this.nextTitle = title;
|
||||
code.run();
|
||||
final var response = code.get();
|
||||
this.nextTitle = null;
|
||||
return response;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpGet(final String uriPath) {
|
||||
public final HttpResponse httpGet(final String uriPathWithPlaceholders) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.GET()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
@ -132,7 +146,8 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPost(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
||||
public final HttpResponse httpPost(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.POST(BodyPublishers.ofString(requestBody))
|
||||
@ -146,7 +161,8 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpPatch(final String uriPath, final JsonTemplate bodyJsonTemplate) {
|
||||
public final HttpResponse httpPatch(final String uriPathWithPlaceholders, final JsonTemplate bodyJsonTemplate) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var requestBody = bodyJsonTemplate.resolvePlaceholders();
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.method(HttpMethod.PATCH.toString(), BodyPublishers.ofString(requestBody))
|
||||
@ -160,7 +176,8 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public final HttpResponse httpDelete(final String uriPath) {
|
||||
public final HttpResponse httpDelete(final String uriPathWithPlaceholders) {
|
||||
final var uriPath = ScenarioTest.resolve(uriPathWithPlaceholders);
|
||||
final var request = HttpRequest.newBuilder()
|
||||
.DELETE()
|
||||
.uri(new URI("http://localhost:" + testSuite.port + uriPath))
|
||||
@ -172,12 +189,27 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
return new HttpResponse(HttpMethod.DELETE, uriPath, null, response);
|
||||
}
|
||||
|
||||
protected PathAssertion path(final String path) {
|
||||
return new PathAssertion(path);
|
||||
}
|
||||
|
||||
protected void verify(
|
||||
final String title,
|
||||
final Supplier<UseCase.HttpResponse> http,
|
||||
final Consumer<UseCase.HttpResponse>... assertions) {
|
||||
withTitle(ScenarioTest.resolve(title), () -> {
|
||||
final var response = http.get();
|
||||
Arrays.stream(assertions).forEach(assertion -> assertion.accept(response));
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
public final UUID uuid(final String alias) {
|
||||
return ScenarioTest.uuid(alias);
|
||||
}
|
||||
|
||||
public String uriEncoded(final String text) {
|
||||
return encode(ScenarioTest.resolve(text));
|
||||
return encode(ScenarioTest.resolve(text), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static class JsonTemplate {
|
||||
@ -193,7 +225,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
}
|
||||
}
|
||||
|
||||
public class HttpResponse {
|
||||
public final class HttpResponse {
|
||||
|
||||
@Getter
|
||||
private final java.net.http.HttpResponse<String> response;
|
||||
@ -232,7 +264,7 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void keep(final Function<HttpResponse, String> extractor) {
|
||||
public HttpResponse keep(final Function<HttpResponse, String> extractor) {
|
||||
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
||||
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
||||
|
||||
@ -240,14 +272,16 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
ScenarioTest.putAlias(
|
||||
alias,
|
||||
new ScenarioTest.Alias<>(UseCase.this.getClass(), UUID.fromString(value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public void keep() {
|
||||
public HttpResponse keep() {
|
||||
final var alias = nextTitle != null ? nextTitle : resultAlias;
|
||||
assertThat(alias).as("cannot keep result, no alias found").isNotNull();
|
||||
ScenarioTest.putAlias(
|
||||
alias,
|
||||
new ScenarioTest.Alias<>(UseCase.this.getClass(), locationUuid));
|
||||
return this;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -263,7 +297,21 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
|
||||
@SneakyThrows
|
||||
public String getFromBody(final String path) {
|
||||
return JsonPath.parse(response.body()).read(path);
|
||||
return JsonPath.parse(response.body()).read(ScenarioTest.resolve(path));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Optional<String> getFromBodyAsOptional(final String path) {
|
||||
try {
|
||||
return Optional.ofNullable(JsonPath.parse(response.body()).read(ScenarioTest.resolve(path)));
|
||||
} catch (final Exception e) {
|
||||
return null; // means the property did not exist at all, not that it was there with value null
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public OptionalAssert<String> path(final String path) {
|
||||
return assertThat(getFromBodyAsOptional(path));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@ -274,6 +322,8 @@ public abstract class UseCase<T extends UseCase<?>> {
|
||||
testReport.printLine("\n### " + nextTitle + "\n");
|
||||
} else if (resultAlias != null) {
|
||||
testReport.printLine("\n### " + resultAlias + "\n");
|
||||
} else {
|
||||
fail("please wrap the http...-call in the UseCase using `withTitle(...)`");
|
||||
}
|
||||
|
||||
// the request
|
||||
|
@ -0,0 +1,16 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios;
|
||||
|
||||
import static org.assertj.core.api.Assumptions.assumeThat;
|
||||
|
||||
public class UseCaseNotImplementedYet extends UseCase<UseCaseNotImplementedYet> {
|
||||
|
||||
public UseCaseNotImplementedYet(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
assumeThat(false).isTrue(); // makes the test gray
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.contact;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class AddPhoneNumberToContactData extends UseCase<AddPhoneNumberToContactData> {
|
||||
|
||||
public AddPhoneNumberToContactData(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain(
|
||||
"partnerContactUuid",
|
||||
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
withTitle("Patch the Additional Phone-Number into the Contact", () ->
|
||||
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
|
||||
{
|
||||
"phoneNumbers": {
|
||||
${phoneNumberKeyToAdd}: ${phoneNumberToAdd}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.OK)
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify() {
|
||||
verify(
|
||||
"Verify if the New Phone Number Got Added",
|
||||
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON).expectArrayElements(1),
|
||||
path("[0].contact.phoneNumbers.%{phoneNumberKeyToAdd}").contains("%{phoneNumberToAdd}")
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.contact;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class AmendContactData extends UseCase<AmendContactData> {
|
||||
|
||||
public AmendContactData(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("partnerContactUuid", () ->
|
||||
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
withTitle("Patch the New Phone Number Into the Contact", () ->
|
||||
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
|
||||
{
|
||||
"caption": ${newContactCaption???},
|
||||
"postalAddress": ${newPostalAddress???},
|
||||
"phoneNumbers": {
|
||||
"office": ${newOfficePhoneNumber???}
|
||||
},
|
||||
"emailAddresses": {
|
||||
"main": ${newEmailAddress???}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.OK)
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.contact;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class RemovePhoneNumberFromContactData extends UseCase<RemovePhoneNumberFromContactData> {
|
||||
|
||||
public RemovePhoneNumberFromContactData(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain(
|
||||
"partnerContactUuid",
|
||||
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].contact.uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
withTitle("Patch the Additional Phone-Number into the Contact", () ->
|
||||
httpPatch("/api/hs/office/contacts/%{partnerContactUuid}", usingJsonBody("""
|
||||
{
|
||||
"phoneNumbers": {
|
||||
${phoneNumberKeyToRemove}: NULL
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(HttpStatus.OK)
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify() {
|
||||
verify(
|
||||
"Verify if the New Phone Number Got Added",
|
||||
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON).expectArrayElements(1),
|
||||
path("[0].contact.phoneNumbers.%{phoneNumberKeyToRemove}").doesNotExist()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package net.hostsharing.hsadminng.hs.office.scenarios.contact;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
|
||||
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
|
||||
|
||||
import static io.restassured.http.ContentType.JSON;
|
||||
import static org.springframework.http.HttpStatus.CREATED;
|
||||
import static org.springframework.http.HttpStatus.OK;
|
||||
|
||||
public class ReplaceContactData extends UseCase<ReplaceContactData> {
|
||||
|
||||
public ReplaceContactData(final ScenarioTest testSuite) {
|
||||
super(testSuite);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpResponse run() {
|
||||
|
||||
obtain("partnerRelationUuid", () ->
|
||||
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("Contact: %{newContactCaption}", () ->
|
||||
httpPost("/api/hs/office/contacts", usingJsonBody("""
|
||||
{
|
||||
"caption": ${newContactCaption},
|
||||
"postalAddress": ${newPostalAddress???},
|
||||
"phoneNumbers": {
|
||||
"phone": ${newOfficePhoneNumber???}
|
||||
},
|
||||
"emailAddresses": {
|
||||
"main": ${newEmailAddress???}
|
||||
}
|
||||
}
|
||||
"""))
|
||||
.expecting(CREATED).expecting(JSON),
|
||||
"Please check first if that contact already exists, if so, use it's UUID below."
|
||||
);
|
||||
|
||||
withTitle("Replace the Contact-Reference in the Partner-Relation", () ->
|
||||
httpPatch("/api/hs/office/relations/%{partnerRelationUuid}", usingJsonBody("""
|
||||
{
|
||||
"contactUuid": ${Contact: %{newContactCaption}}
|
||||
}
|
||||
"""))
|
||||
.expecting(OK)
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verify() {
|
||||
verify(
|
||||
"Verify if the Contact-Relation Got Replaced in the Partner-Relation",
|
||||
() -> httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerName}"))
|
||||
.expecting(OK).expecting(JSON).expectArrayElements(1),
|
||||
path("[0].contact.caption").contains("%{newContactCaption}")
|
||||
);
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ public class CreateExternalDebitorForPartner extends UseCase<CreateExternalDebit
|
||||
httpGet("/api/hs/office/persons?name=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("BankAccount: Billing GmbH - refund bank account", () ->
|
||||
|
@ -19,8 +19,7 @@ public class CreateSelfDebitorForPartner extends UseCase<CreateSelfDebitorForPar
|
||||
httpGet("/api/hs/office/relations?relationType=PARTNER&personData=" + uriEncoded("%{partnerPersonTradeName}"))
|
||||
.expecting(OK).expecting(JSON),
|
||||
response -> response.expectArrayElements(1).getFromBody("[0].holder.uuid"),
|
||||
"In production data this query could result in multiple outputs. In that case, you have to find out which is the right one.",
|
||||
"**HINT**: With production data, you might get multiple results and have to decide which is the right one."
|
||||
"In production, data this query could result in multiple outputs. In that case, you have to find out which is the right one."
|
||||
);
|
||||
|
||||
obtain("BankAccount: Test AG - refund bank account", () ->
|
||||
|