├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── maven.yml
├── .gitignore
├── .mvn
└── wrapper
│ ├── MavenWrapperDownloader.java
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── Dockerfile
├── Procfile
├── README.md
├── app.json
├── build.gradle.kts
├── docker-compose.yml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mvnw
├── mvnw.cmd
├── pom.xml
├── renovate.json
├── settings.gradle.kts
├── src
├── changes
│ └── changes.xml
├── site
│ └── site.xml
└── sonar
│ ├── checkstyle.xml
│ ├── findbugs.xml
│ └── pmd.xml
├── system.properties
├── worblehat-acceptancetests
├── .checkstyle
├── infinitest.filters
├── pom.xml
└── src
│ ├── site
│ └── site.xml
│ └── test
│ ├── java
│ └── de
│ │ └── codecentric
│ │ └── psd
│ │ └── worblehat
│ │ ├── AcceptanceTestsIT.java
│ │ ├── TagFocusIT.java
│ │ └── acceptancetests
│ │ ├── adapter
│ │ ├── ParameterTypes.java
│ │ ├── SeleniumAdapter.java
│ │ ├── SpringAdapter.java
│ │ └── wrapper
│ │ │ ├── HtmlBook.java
│ │ │ ├── HtmlBookList.java
│ │ │ ├── Page.java
│ │ │ └── PageElement.java
│ │ └── step
│ │ ├── StoryContext.java
│ │ ├── business
│ │ ├── DemoBookFactory.java
│ │ └── Library.java
│ │ └── page
│ │ ├── BookDetailsPage.java
│ │ ├── BookList.java
│ │ ├── BorrowBook.java
│ │ ├── InsertBook.java
│ │ ├── ListBorrowedBooksPage.java
│ │ └── ReturnAllBooks.java
│ └── resources
│ ├── application.properties
│ ├── cucumber.properties
│ └── de
│ └── codecentric
│ └── psd
│ └── worblehat
│ └── features
│ └── book
│ ├── Add Book.feature
│ ├── All Borrow Books.feature
│ ├── Book Details.feature
│ ├── Book Not Added.feature
│ ├── Borrow Book.feature
│ ├── Remove Book.feature
│ └── Return All Books.feature
├── worblehat-domain
├── .checkstyle
├── .eclipse-pmd
├── build.gradle.kts
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── de
│ │ │ └── codecentric
│ │ │ └── psd
│ │ │ └── worblehat
│ │ │ └── domain
│ │ │ ├── Book.java
│ │ │ ├── BookParameter.java
│ │ │ ├── BookRepository.java
│ │ │ ├── BookService.java
│ │ │ ├── Borrowing.java
│ │ │ ├── BorrowingRepository.java
│ │ │ └── StandardBookService.java
│ └── resources
│ │ ├── de
│ │ └── codecentric
│ │ │ └── psd
│ │ │ └── worblehat
│ │ │ └── liquibase-changesets
│ │ │ ├── version-1.0
│ │ │ ├── 001-initialize-db.sql
│ │ │ └── 002-remove-unique-isbn-constraint.sql
│ │ │ └── version-1.2
│ │ │ ├── 001-add-description.sql
│ │ │ └── 002-halt-on-invalid-book-data-for-isbn-editions.xml
│ │ └── master.xml
│ ├── site
│ └── site.xml
│ └── test
│ └── java
│ └── de
│ └── codecentric
│ └── psd
│ └── worblehat
│ └── domain
│ ├── BookTest.java
│ ├── BorrowingTest.java
│ └── StandardBookServiceTest.java
└── worblehat-web
├── .checkstyle
├── .eclipse-pmd
├── build.gradle.kts
├── pom.xml
└── src
├── main
├── java
│ └── de
│ │ └── codecentric
│ │ └── psd
│ │ ├── Worblehat.java
│ │ └── worblehat
│ │ └── web
│ │ ├── controller
│ │ ├── BookDetailsController.java
│ │ ├── BookListController.java
│ │ ├── BorrowBookController.java
│ │ ├── BorrowedBookListController.java
│ │ ├── ControllerSetup.java
│ │ ├── InsertBookController.java
│ │ ├── NavigationController.java
│ │ ├── RemoveBookController.java
│ │ └── ReturnAllBooksController.java
│ │ ├── formdata
│ │ ├── BorrowBookFormData.java
│ │ ├── InsertBookFormData.java
│ │ └── ReturnAllBooksFormData.java
│ │ └── validation
│ │ ├── ISBN.java
│ │ ├── ISBNConstraintValidator.java
│ │ ├── Numeric.java
│ │ └── NumericConstraintValidator.java
└── resources
│ ├── ValidationMessages.properties
│ ├── application.properties
│ ├── banner.txt
│ ├── logback.xml
│ ├── messages.properties
│ └── templates
│ ├── bookDetails.html
│ ├── bookList.html
│ ├── borrow.html
│ ├── borrowedBookList.html
│ ├── fragments
│ └── footer.html
│ ├── home.html
│ ├── insertBooks.html
│ └── returnAllBooks.html
├── site
└── site.xml
└── test
├── java
└── de
│ └── codecentric
│ └── psd
│ ├── WorblehatDev.java
│ ├── WorblehatSystemIT.java
│ └── worblehat
│ └── web
│ ├── controller
│ ├── BookDetailsControllerTest.java
│ ├── BookListControllerTest.java
│ ├── BorrowBookControllerTest.java
│ ├── BorrowedBookListControllerTest.java
│ ├── BorrowingTestData.java
│ ├── InsertBookControllerTest.java
│ ├── NavigationControllerTest.java
│ ├── RemoveBookControllerTest.java
│ ├── ReturnAllBooksControllerTest.java
│ └── ReturnAllBooksFormTestData.java
│ └── validation
│ ├── ISBNConstraintValidatorTest.java
│ └── NumericConstraintValidatorTest.java
└── resources
├── application.properties
└── log4j.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | max_line_length = 120
11 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | #
2 | # https://help.github.com/articles/dealing-with-line-endings/
3 | #
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
7 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | push:
8 | branches: ["master", "production"]
9 | pull_request:
10 | branches: ["master"]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Set up JDK 15
20 | uses: actions/setup-java@v2
21 | with:
22 | java-version: 15
23 | distribution: 'adopt'
24 |
25 | - name: Cache Maven packages
26 | uses: actions/cache@v2
27 | with:
28 | path: ~/.m2
29 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
30 | restore-keys: ${{ runner.os }}-m2
31 |
32 | - name: Maven Install & Unit Tests
33 | run: ./mvnw install
34 |
35 | - name: Publish Unit Test Results
36 | uses: EnricoMi/publish-unit-test-result-action@v1.40
37 | if: always()
38 | with:
39 | github_token: ${{ secrets.GITHUB_TOKEN }}
40 | check_name: Unit Test Results
41 | files: worblehat-*/target/surefire-reports/TEST*.xml
42 | report_individual_runs: true
43 | deduplicate_classes_by_file_name: false
44 |
45 | - name: Run Acceptance Tests
46 | run: ./mvnw -P runITs verify
47 |
48 | - name: Publish Acceptance Test Results
49 | uses: EnricoMi/publish-unit-test-result-action@v1.40
50 | if: always()
51 | with:
52 | github_token: ${{ secrets.GITHUB_TOKEN }}
53 | check_name: Acceptance Test Results
54 | files: worblehat-acceptancetests/target/cucumber.xml
55 | report_individual_runs: true
56 | deduplicate_classes_by_file_name: false
57 |
58 | - uses: actions/upload-artifact@v2
59 | with:
60 | name: Video
61 | path: worblehat-acceptancetests/target/*.flv
62 | - uses: actions/upload-artifact@v2
63 | with:
64 | name: Acceptance Test Report
65 | path: worblehat-acceptancetests/target/cucumber.html
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | bin/
3 | .project
4 | .classpath
5 | .metadata
6 | .factorypath
7 | local.properties
8 | .settings/
9 | .vscode/
10 | *.versionsBackup
11 | *.idea
12 | *.iml
13 | *.db
14 | logs
15 | .DS_Store
16 |
17 | # Ignore Gradle project-specific cache directory
18 | .gradle
19 |
20 | # Ignore Gradle build output directory
21 | build
22 |
--------------------------------------------------------------------------------
/.mvn/wrapper/MavenWrapperDownloader.java:
--------------------------------------------------------------------------------
1 | /*
2 | Licensed to the Apache Software Foundation (ASF) under one
3 | or more contributor license agreements. See the NOTICE file
4 | distributed with this work for additional information
5 | regarding copyright ownership. The ASF licenses this file
6 | to you under the Apache License, Version 2.0 (the
7 | "License"); you may not use this file except in compliance
8 | with the License. You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing,
13 | software distributed under the License is distributed on an
14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | KIND, either express or implied. See the License for the
16 | specific language governing permissions and limitations
17 | under the License.
18 | */
19 |
20 | import java.net.*;
21 | import java.io.*;
22 | import java.nio.channels.*;
23 | import java.util.Properties;
24 |
25 | public class MavenWrapperDownloader {
26 |
27 | /**
28 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
29 | */
30 | private static final String DEFAULT_DOWNLOAD_URL =
31 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
32 |
33 | /**
34 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
35 | * use instead of the default one.
36 | */
37 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
38 | ".mvn/wrapper/maven-wrapper.properties";
39 |
40 | /**
41 | * Path where the maven-wrapper.jar will be saved to.
42 | */
43 | private static final String MAVEN_WRAPPER_JAR_PATH =
44 | ".mvn/wrapper/maven-wrapper.jar";
45 |
46 | /**
47 | * Name of the property which should be used to override the default download url for the wrapper.
48 | */
49 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
50 |
51 | public static void main(String args[]) {
52 | System.out.println("- Downloader started");
53 | File baseDirectory = new File(args[0]);
54 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
55 |
56 | // If the maven-wrapper.properties exists, read it and check if it contains a custom
57 | // wrapperUrl parameter.
58 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
59 | String url = DEFAULT_DOWNLOAD_URL;
60 | if(mavenWrapperPropertyFile.exists()) {
61 | FileInputStream mavenWrapperPropertyFileInputStream = null;
62 | try {
63 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
64 | Properties mavenWrapperProperties = new Properties();
65 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
66 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
67 | } catch (IOException e) {
68 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
69 | } finally {
70 | try {
71 | if(mavenWrapperPropertyFileInputStream != null) {
72 | mavenWrapperPropertyFileInputStream.close();
73 | }
74 | } catch (IOException e) {
75 | // Ignore ...
76 | }
77 | }
78 | }
79 | System.out.println("- Downloading from: : " + url);
80 |
81 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
82 | if(!outputFile.getParentFile().exists()) {
83 | if(!outputFile.getParentFile().mkdirs()) {
84 | System.out.println(
85 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
86 | }
87 | }
88 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
89 | try {
90 | downloadFileFromURL(url, outputFile);
91 | System.out.println("Done");
92 | System.exit(0);
93 | } catch (Throwable e) {
94 | System.out.println("- Error downloading");
95 | e.printStackTrace();
96 | System.exit(1);
97 | }
98 | }
99 |
100 | private static void downloadFileFromURL(String urlString, File destination) throws Exception {
101 | URL website = new URL(urlString);
102 | ReadableByteChannel rbc;
103 | rbc = Channels.newChannel(website.openStream());
104 | FileOutputStream fos = new FileOutputStream(destination);
105 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
106 | fos.close();
107 | rbc.close();
108 | }
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scrum-for-developers/worblehat-youtube/902ba7fe4b959d20a8ab86328437b875ab7c2375/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:3.8.1-openjdk-15-slim AS build
2 |
3 | WORKDIR /usr/local/src/worblehat_youtube
4 | COPY . /usr/local/src/worblehat_youtube
5 |
6 | RUN mvn clean install
7 |
8 | FROM adoptopenjdk/openjdk15:jre-15.0.2_7-ubi-minimal
9 |
10 | ENV PORT=8080 DATABASE_PORT=5432 DATABASE_HOST=localhost
11 |
12 | EXPOSE $PORT
13 |
14 | COPY --from=build /usr/local/src/worblehat_youtube/worblehat-web/target/*.jar /worblehat-web/
15 |
16 | CMD java -Dserver.port=$PORT -jar /worblehat-web/*.jar --spring.datasource.url=jdbc:postgresql://$DATABASE_HOST:$DATABASE_PORT/postgres
17 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java -Dserver.port=$PORT $JAVA_OPTS -jar worblehat-web/target/*.jar
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Worblehat
2 |
3 | [](https://travis-ci.org/scrum-for-developers/worblehat-youtube)
4 | [](https://floobits.com/AndreasEK/worblehat-youtube/redirect)
5 |
6 | Worblehat is a training application for the [Scrum for Developers](https://github.com/scrum-for-developers) training
7 | held by [codecentric AG](https://www.codecentric.de/), as well as for the [Professional Scrum Developer](https://www.codecentric.de/schulung/professional-scrum-developer/#schulung-detail) training when given by codecentric.
8 |
9 | ## YouTube Let's Code with Bene and Andreas
10 |
11 | In a [series of livestreams](https://www.youtube.com/playlist?list=PLD9VybHH2wnY6AdGpGinjwzq5brwRn85K), Bene and Andreas will implement the complete Backlog that is usually used during the training.
12 |
13 | You can vote on the issues in order to influence the order of the Product Backlog. Please react with a "Thumbs Up" to the issue that You are interested in to see.
14 |
15 | * [Most voted issues](https://github.com/scrum-for-developers/worblehat-youtube/issues?utf8=%E2%9C%93&q=is%3Aopen+sort%3Areactions-%2B1-desc)
16 | * [Task Board / Product Backlog](https://github.com/scrum-for-developers/worblehat-youtube/projects/1)
17 |
18 | ## Developing the application
19 |
20 | Technical requirements:
21 |
22 | * JDK 11+ (not required when using docker)
23 | * Docker
24 |
25 | ### Complete setup
26 |
27 | The whole service can be started locally using docker-compose:
28 |
29 | ```shell
30 | docker-compose up
31 | ```
32 |
33 | After updating sources always rebuild your images
34 | ```shell
35 | docker-compose down
36 | docker-compose build
37 | docker-compose up
38 | ```
39 |
40 | ### Data base setup
41 |
42 | A PostgreSQL data base can be started locally using docker-compose:
43 |
44 | ```shell
45 | docker-compose up db
46 | ```
47 |
48 | The `adminer` and `database` services can be started locally using docker-compose:
49 |
50 | ```shell
51 | docker-compose up adminer
52 | ```
53 |
54 | The docker compose setup includes [Adminer](https://www.adminer.org) for adminstrating the data base.
55 | Once the data base is started point your broser to http://localhost:8081 and log into the data base:
56 |
57 | | Setting | Value |
58 | |------------------|--------------|
59 | | Data base system | PostgreSQL |
60 | | Server | db |
61 | | User | postgres |
62 | | Password | worblehat-pw |
63 | | Data base | postgres |
64 |
65 | ### Build process
66 |
67 | You can use the maven wrapper to compile and execute the application, when using `vscode` make sure to install the [Lombok](https://marketplace.visualstudio.com/items?itemName=GabrielBB.vscode-lombok) extension
68 |
69 | * Compile everything: `./mvnw clean install`
70 | * Run the application: `./mvnw -pl worblehat-web spring-boot:run`
71 | * Run the acceptancetests: `./mvnw -P runITs verify`
72 |
73 | Maven comes bundled with the maven wrapper scripts, no need for manual installation before.
74 |
75 | ## Running the application
76 |
77 | 1. Make sure the database is running (see above)
78 | 1. Run the application.:
79 | * Either run `./mvnw -pl worblehat-web spring-boot:run` (will automatically compile & package the application before)
80 | * Or use your IDE to start the main class in worblehat-web: `de.codecentric.psd.Worblehat`
81 | 1. Access the application at
82 |
83 | ## Running tests
84 |
85 | All tests are executed via JUnit, but can be conceptually divided in unit and integration tests. They are bound to different maven lifecycle phases, are executed by differen maven plugins, and follow a different naming scheme.
86 |
87 | ### Unit Tests
88 |
89 | 1. Unit tests are run with `./mvnw test`
90 | 1. The [maven-surefire-plugin](https://maven.apache.org/surefire/maven-surefire-plugin) includes
91 | [all these tests](https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html#includes) by default:
92 |
93 | ```xml
94 |
95 | **/Test*.java
96 | **/*Test.java
97 | **/*Tests.java
98 | **/*TestCase.java
99 |
100 | ```
101 |
102 | ### Acceptance Tests
103 |
104 | 1. Acceptance tests are run by activating the required profile `./mvnw -P runITs verify`.
105 |
106 | Note: The `verify` lifecycle is executed before `install`. Integration tests are only included, if the `runITs` profile is activated.
107 |
108 | 1. The [maven-failsafe-plugin](https://maven.apache.org/surefire/maven-failsafe-plugin) includes
109 | [all these tests](https://maven.apache.org/surefire/maven-failsafe-plugin/integration-test-mojo.html#includes) by default:
110 |
111 | ```xml
112 |
113 | **/IT*.java
114 | **/*IT.java
115 | **/*ITCase.java
116 |
117 | ```
118 |
119 | The acceptance tests spin docker containers for all required dependencies (Database & Browser) via [Testcontainers](https://www.testcontainers.org/).
120 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "worblehat-youtube",
3 | "scripts": {
4 | },
5 | "env": {
6 | },
7 | "formation": {
8 | "web": {
9 | "quantity": 1
10 | }
11 | },
12 | "addons": [
13 | "heroku-postgresql"
14 | ],
15 | "buildpacks": [
16 | {
17 | "url": "heroku/java"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * This is a general purpose Gradle build.
5 | * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds
6 | */
7 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 |
3 | services:
4 | db:
5 | image: postgres
6 | restart: unless-stopped
7 | ports:
8 | - "5432:5432"
9 | environment:
10 | POSTGRES_PASSWORD: worblehat-pw
11 |
12 | adminer:
13 | image: adminer
14 | restart: unless-stopped
15 | ports:
16 | - "8081:8080"
17 | depends_on:
18 | - db
19 |
20 | worblehat:
21 | build:
22 | context: .
23 | dockerfile: ./Dockerfile
24 | environment:
25 | DATABASE_HOST: db
26 | DATABASE_PORT: 5432
27 | ports:
28 | - 8080:8080
29 | depends_on:
30 | - db
31 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | version=1.3.1-SNAPSHOT
2 | applicationName=Worblehat Bookmanager
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scrum-for-developers/worblehat-youtube/902ba7fe4b959d20a8ab86328437b875ab7c2375/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
147 | # shellcheck disable=SC3045
148 | MAX_FD=$( ulimit -H -n ) ||
149 | warn "Could not query maximum file descriptor limit"
150 | esac
151 | case $MAX_FD in #(
152 | '' | soft) :;; #(
153 | *)
154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
155 | # shellcheck disable=SC3045
156 | ulimit -n "$MAX_FD" ||
157 | warn "Could not set maximum file descriptor limit to $MAX_FD"
158 | esac
159 | fi
160 |
161 | # Collect all arguments for the java command, stacking in reverse order:
162 | # * args from the command line
163 | # * the main class name
164 | # * -classpath
165 | # * -D...appname settings
166 | # * --module-path (only if needed)
167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
168 |
169 | # For Cygwin or MSYS, switch paths to Windows format before running java
170 | if "$cygwin" || "$msys" ; then
171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
173 |
174 | JAVACMD=$( cygpath --unix "$JAVACMD" )
175 |
176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
177 | for arg do
178 | if
179 | case $arg in #(
180 | -*) false ;; # don't mess with options #(
181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
182 | [ -e "$t" ] ;; #(
183 | *) false ;;
184 | esac
185 | then
186 | arg=$( cygpath --path --ignore --mixed "$arg" )
187 | fi
188 | # Roll the args list around exactly as many times as the number of
189 | # args, so each arg winds up back in the position where it started, but
190 | # possibly modified.
191 | #
192 | # NB: a `for` loop captures its iteration list before it begins, so
193 | # changing the positional parameters here affects neither the number of
194 | # iterations, nor the values presented in `arg`.
195 | shift # remove old arg
196 | set -- "$@" "$arg" # push replacement arg
197 | done
198 | fi
199 |
200 | # Collect all arguments for the java command;
201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
202 | # shell script including quotes and variable substitutions, so put them in
203 | # double quotes to make sure that they get re-expanded; and
204 | # * put everything else in single quotes, so that it's not re-expanded.
205 |
206 | set -- \
207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
208 | -classpath "$CLASSPATH" \
209 | org.gradle.wrapper.GradleWrapperMain \
210 | "$@"
211 |
212 | # Stop when "xargs" is not available.
213 | if ! command -v xargs >/dev/null 2>&1
214 | then
215 | die "xargs is not available"
216 | fi
217 |
218 | # Use "xargs" to parse quoted args.
219 | #
220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
221 | #
222 | # In Bash we could simply go:
223 | #
224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
225 | # set -- "${ARGS[@]}" "$@"
226 | #
227 | # but POSIX shell has neither arrays nor command substitution, so instead we
228 | # post-process each arg (as a line of input to sed) to backslash-escape any
229 | # character that might be a shell metacharacter, then use eval to reverse
230 | # that process (while maintaining the separation between arguments), and wrap
231 | # the whole thing up as a single "set" statement.
232 | #
233 | # This will of course break if any of these variables contains a newline or
234 | # an unmatched quote.
235 | #
236 |
237 | eval "set -- $(
238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
239 | xargs -n1 |
240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
241 | tr '\n' ' '
242 | )" '"$@"'
243 |
244 | exec "$JAVACMD" "$@"
245 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM set title of command window
39 | title %0
40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
42 |
43 | @REM set %HOME% to equivalent of $HOME
44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
45 |
46 | @REM Execute a user defined script before this one
47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
51 | :skipRcPre
52 |
53 | @setlocal
54 |
55 | set ERROR_CODE=0
56 |
57 | @REM To isolate internal variables from possible post scripts, we use another setlocal
58 | @setlocal
59 |
60 | @REM ==== START VALIDATION ====
61 | if not "%JAVA_HOME%" == "" goto OkJHome
62 |
63 | echo.
64 | echo Error: JAVA_HOME not found in your environment. >&2
65 | echo Please set the JAVA_HOME variable in your environment to match the >&2
66 | echo location of your Java installation. >&2
67 | echo.
68 | goto error
69 |
70 | :OkJHome
71 | if exist "%JAVA_HOME%\bin\java.exe" goto init
72 |
73 | echo.
74 | echo Error: JAVA_HOME is set to an invalid directory. >&2
75 | echo JAVA_HOME = "%JAVA_HOME%" >&2
76 | echo Please set the JAVA_HOME variable in your environment to match the >&2
77 | echo location of your Java installation. >&2
78 | echo.
79 | goto error
80 |
81 | @REM ==== END VALIDATION ====
82 |
83 | :init
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
122 |
123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | echo Found %WRAPPER_JAR%
132 | ) else (
133 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
134 | echo Downloading from: %DOWNLOAD_URL%
135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
136 | echo Finished downloading %WRAPPER_JAR%
137 | )
138 | @REM End of extension
139 |
140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
141 | if ERRORLEVEL 1 goto error
142 | goto end
143 |
144 | :error
145 | set ERROR_CODE=1
146 |
147 | :end
148 | @endlocal & set ERROR_CODE=%ERROR_CODE%
149 |
150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
154 | :skipRcPost
155 |
156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
158 |
159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
160 |
161 | exit /B %ERROR_CODE%
162 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "packageRules": [
6 | {
7 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"],
8 | "automerge": true
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file was generated by the Gradle 'init' task.
3 | *
4 | * The settings file is used to specify which projects to include in your build.
5 | *
6 | * Detailed information about configuring a multi-project build in Gradle can be found
7 | * in the user manual at https://docs.gradle.org/6.4/userguide/multi_project_builds.html
8 | */
9 |
10 | plugins {
11 | id("com.gradle.enterprise").version("3.17.5")
12 | }
13 |
14 | rootProject.name = "worblehat"
15 |
16 | include(":worblehat-domain")
17 | include(":worblehat-web")
18 |
19 | gradleEnterprise {
20 | buildScan {
21 | termsOfServiceUrl = "https://gradle.com/terms-of-service"
22 | termsOfServiceAgree = "yes"
23 |
24 | publishAlways()
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/changes/changes.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | Changes for Worblehat Webapp
6 | Markus Bonsch
7 |
8 |
9 |
10 |
11 | Adding new books.
12 |
13 |
14 |
16 |
17 | Borrowing books.
18 |
19 |
20 |
21 |
23 |
24 | Return all borrowed books by one member.
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
26 |
27 |
28 | org.apache.maven.skins
29 | maven-fluido-skin
30 | 1.8
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
71 |
72 |
--------------------------------------------------------------------------------
/src/sonar/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/sonar/pmd.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3
5 |
6 |
7 | 3
8 |
9 |
10 | 3
11 |
12 |
13 | 3
14 |
15 |
16 | 3
17 |
18 |
19 | 5
20 |
21 |
22 | 3
23 |
24 |
25 | 3
26 |
27 |
28 | 3
29 |
30 |
31 | 3
32 |
33 |
34 | 3
35 |
36 |
37 | 3
38 |
39 |
40 | 3
41 |
42 |
43 | 3
44 |
45 |
46 | 3
47 |
48 |
49 | 3
50 |
51 |
52 | 3
53 |
54 |
55 | 3
56 |
57 |
58 | 3
59 |
60 |
61 | 3
62 |
63 |
64 | 5
65 |
66 |
67 | 3
68 |
69 |
70 | 3
71 |
72 |
73 | 3
74 |
75 |
76 | 5
77 |
78 |
79 | 3
80 |
81 |
82 | 3
83 |
84 |
85 | 2
86 |
87 |
88 | 3
89 |
90 |
91 | 4
92 |
93 |
94 | 3
95 |
96 |
97 | 3
98 |
99 |
100 | 3
101 |
102 |
103 | 3
104 |
105 |
106 | 3
107 |
108 |
109 | 3
110 |
111 |
112 | 3
113 |
114 |
115 | 2
116 |
117 |
118 | 5
119 |
120 |
121 | 3
122 |
123 |
124 | 5
125 |
126 |
127 | 5
128 |
129 |
130 | 3
131 |
132 |
133 | 3
134 |
135 |
136 | 3
137 |
138 |
139 | 3
140 |
141 |
142 | 3
143 |
144 |
145 | 3
146 |
147 |
148 | 3
149 |
150 |
151 | 3
152 |
153 |
154 | 3
155 |
156 |
157 | 5
158 |
159 |
160 | 3
161 |
162 |
163 |
164 |
165 |
166 | 3
167 |
168 |
169 | 3
170 |
171 |
172 | 3
173 |
174 |
175 | 3
176 |
177 |
178 |
179 |
180 |
181 | 3
182 |
183 |
184 | 5
185 |
186 |
187 | 3
188 |
189 |
190 | 3
191 |
192 |
193 | 3
194 |
195 |
196 | 3
197 |
198 |
199 | 3
200 |
201 |
202 | 3
203 |
204 |
205 | 3
206 |
207 |
208 | 3
209 |
210 |
211 | 5
212 |
213 |
214 | 3
215 |
216 |
217 | 3
218 |
219 |
220 | 3
221 |
222 |
223 | 5
224 |
225 |
226 | 3
227 |
228 |
229 | 3
230 |
231 |
232 | 3
233 |
234 |
235 | 3
236 |
237 |
238 | 3
239 |
240 |
241 | 3
242 |
243 |
244 | 3
245 |
246 |
247 | 3
248 |
249 |
250 | 5
251 |
252 |
253 | 3
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/system.properties:
--------------------------------------------------------------------------------
1 | java.runtime.version=15
2 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/.checkstyle:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/infinitest.filters:
--------------------------------------------------------------------------------
1 | .*Stories
2 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/pom.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | de.codecentric.psd
6 | worblehat
7 | 1.4.0-SNAPSHOT
8 | ../
9 |
10 |
11 | 4.0.0
12 | worblehat-acceptancetests
13 | Worblehat Automated Acceptance Tests
14 |
15 |
16 | 6.11.0
17 | 3.141.59
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-test
27 | test
28 |
29 |
30 | io.cucumber
31 | cucumber-java
32 | ${cucumber.version}
33 | test
34 |
35 |
36 | io.cucumber
37 | cucumber-junit
38 | ${cucumber.version}
39 | test
40 |
41 |
42 | io.cucumber
43 | cucumber-spring
44 | ${cucumber.version}
45 | test
46 |
47 |
48 |
49 |
50 | de.codecentric.psd
51 | worblehat-web
52 | ${project.version}
53 | test
54 |
55 |
56 | org.seleniumhq.selenium
57 | selenium-leg-rc
58 | ${selenium.version}
59 | test
60 |
61 |
62 | org.seleniumhq.selenium
63 | selenium-server
64 | ${selenium.version}
65 | test
66 |
67 |
68 | org.apache.commons
69 | commons-lang3
70 | test
71 |
72 |
73 | org.testcontainers
74 | postgresql
75 | test
76 |
77 |
78 | org.testcontainers
79 | selenium
80 | test
81 |
82 |
83 | org.mockito
84 | mockito-core
85 | test
86 |
87 |
88 |
89 |
90 |
91 | org.apache.maven.plugins
92 | maven-failsafe-plugin
93 | 3.3.0
94 |
95 |
96 | **/Tag*IT.java
97 |
98 |
99 |
100 |
102 |
103 | org.apache.maven.surefire
104 | surefire-junit47
105 | 3.3.0
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
26 |
27 |
28 | org.apache.maven.skins
29 | maven-fluido-skin
30 | 1.8
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/AcceptanceTestsIT.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat;
2 |
3 | import io.cucumber.junit.Cucumber;
4 | import io.cucumber.junit.CucumberOptions;
5 | import org.junit.runner.RunWith;
6 |
7 | @RunWith(Cucumber.class)
8 | @CucumberOptions(
9 | strict = true,
10 | stepNotifications = true,
11 | // tags = "@Focus",
12 | plugin = {
13 | "pretty",
14 | "html:target/cucumber.html",
15 | "junit:target/cucumber.xml",
16 | "json:target/cucumber-report.json"
17 | },
18 | publish = false)
19 | public class AcceptanceTestsIT {}
20 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/TagFocusIT.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat;
2 |
3 | import io.cucumber.junit.Cucumber;
4 | import io.cucumber.junit.CucumberOptions;
5 | import org.junit.runner.RunWith;
6 |
7 | @RunWith(Cucumber.class)
8 | @CucumberOptions(
9 | // run dedicated feature files
10 | // features = {"src/test/resources/de/codecentric/psd/worblehat/features/book/Remove
11 | // Book.feature"},
12 | // run feature identified by a tag expression
13 | tags = "@Focus",
14 | strict = true,
15 | stepNotifications = true,
16 | plugin = {
17 | "pretty",
18 | "html:target/cucumber.html",
19 | "junit:target/cucumber.xml",
20 | "json:target/cucumber-report.json"
21 | },
22 | publish = false)
23 | public class TagFocusIT {}
24 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/ParameterTypes.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter;
2 |
3 | import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
4 |
5 | import io.cucumber.java.ParameterType;
6 | import java.time.LocalDate;
7 |
8 | public class ParameterTypes {
9 |
10 | @ParameterType("\\d{4}-\\d{2}-\\d{2}")
11 | public LocalDate date(String aDate) {
12 | return LocalDate.from(ISO_LOCAL_DATE.parse(aDate));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/SeleniumAdapter.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter;
2 |
3 | import static org.testcontainers.containers.BrowserWebDriverContainer.VncRecordingMode.RECORD_ALL;
4 |
5 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.HtmlBookList;
6 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
7 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
8 | import io.cucumber.java.After;
9 | import io.cucumber.java.Before;
10 | import java.io.File;
11 | import java.io.IOException;
12 | import java.text.SimpleDateFormat;
13 | import java.util.ArrayList;
14 | import java.util.Date;
15 | import java.util.List;
16 | import java.util.Optional;
17 | import org.apache.commons.lang3.SystemUtils;
18 | import org.openqa.selenium.*;
19 | import org.openqa.selenium.chrome.ChromeOptions;
20 | import org.slf4j.Logger;
21 | import org.slf4j.LoggerFactory;
22 | import org.springframework.boot.web.server.LocalServerPort;
23 | import org.testcontainers.Testcontainers;
24 | import org.testcontainers.containers.BrowserWebDriverContainer;
25 | import org.testcontainers.lifecycle.TestDescription;
26 |
27 | /** Itegrates Selenium into the tests. */
28 | public class SeleniumAdapter {
29 |
30 | private static final SimpleDateFormat SIMPLE_DATE_FORMAT =
31 | new SimpleDateFormat("yyyy-MM-dd HH:mm");
32 | @LocalServerPort private int port;
33 |
34 | private static final Logger LOGGER = LoggerFactory.getLogger(SeleniumAdapter.class);
35 |
36 | private WebDriver driver;
37 |
38 | private String folderName;
39 |
40 | public void setDriver(WebDriver driver) {
41 | this.driver = driver;
42 | }
43 |
44 | // @ClassRule - not supported by Cucumber at this point
45 | @SuppressWarnings("rawtypes")
46 | public static BrowserWebDriverContainer chromeContainer =
47 | new BrowserWebDriverContainer<>()
48 | .withCapabilities(new ChromeOptions())
49 | .withRecordingMode(RECORD_ALL, new File("./target/"));
50 |
51 | // a class that extends thread that is to be called when program is exiting
52 | static final Thread afterAllThread =
53 | new Thread() {
54 |
55 | @SuppressWarnings("unchecked")
56 | public void run() {
57 | chromeContainer.afterTest(
58 | new TestDescription() {
59 | @Override
60 | public String getTestId() {
61 | return "ID";
62 | }
63 |
64 | @Override
65 | public String getFilesystemFriendlyName() {
66 | return "Worblehat-AcceptanceTests";
67 | }
68 | },
69 | Optional.empty());
70 | }
71 | };
72 |
73 | @Before
74 | public void setup() {
75 | if (!chromeContainer.isRunning()) {
76 | Runtime.getRuntime().addShutdownHook(afterAllThread);
77 | Testcontainers.exposeHostPorts(80, 8080, 9100, 9101, port);
78 | chromeContainer.start();
79 | LOGGER.info("Connect to VNC via " + chromeContainer.getVncAddress());
80 | try {
81 | if (SystemUtils.IS_OS_MAC) {
82 | Runtime.getRuntime().exec("open " + chromeContainer.getVncAddress());
83 | }
84 | if (SystemUtils.IS_OS_LINUX) {
85 | Runtime.getRuntime().exec("krdc " + chromeContainer.getVncAddress());
86 | }
87 | } catch (IOException e) {
88 | // silently fail, if it's not working – e.printStackTrace();
89 | }
90 | }
91 | setDriver(chromeContainer.getWebDriver());
92 | }
93 |
94 | @Before
95 | public void initSelenium() {
96 | folderName =
97 | "target"
98 | + File.separator
99 | + "screenshots"
100 | + File.separator
101 | + SIMPLE_DATE_FORMAT.format(new Date())
102 | + File.separator;
103 | ;
104 | new File(folderName).mkdirs();
105 | }
106 |
107 | public void gotoPage(Page page) {
108 | goToUrl(page.getUrl());
109 | }
110 |
111 | public void gotoPageWithParameter(Page page, String parameter) {
112 | String url = page.getUrl(parameter);
113 | goToUrl(url);
114 | }
115 |
116 | private void goToUrl(String url) {
117 | String concreteUrl = "http://host.testcontainers.internal:" + port + "/" + url;
118 | driver.get(concreteUrl);
119 | if (driver.getPageSource().contains("Whitelabel Error Page"))
120 | throw new IllegalStateException("Page could not be found: " + url);
121 | }
122 |
123 | public void typeIntoField(String id, String value) {
124 | WebElement element = driver.findElement(By.id(id));
125 | element.clear();
126 | element.sendKeys(value);
127 | }
128 |
129 | public HtmlBookList getTableContent(PageElement pageElement) {
130 | WebElement table = driver.findElement(By.className(pageElement.getElementId()));
131 | return new HtmlBookList(table);
132 | }
133 |
134 | public void clickOnPageElementById(PageElement pageElement) {
135 | WebElement element = driver.findElement(By.id(pageElement.getElementId()));
136 | element.click();
137 | }
138 |
139 | public void clickOnPageElementByClassName(String className) {
140 | WebElement element = driver.findElement(By.className(className));
141 | element.click();
142 | }
143 |
144 | public List findAllStringsForElement(PageElement pageElement) {
145 | List webElements = driver.findElements(By.className(pageElement.getElementId()));
146 | List strings = new ArrayList<>();
147 | for (WebElement element : webElements) {
148 | strings.add(element.getText());
149 | }
150 | return strings;
151 | }
152 |
153 | @After
154 | public void afterAnyScenario() {
155 | driver.manage().deleteAllCookies();
156 | }
157 |
158 | public String getTextFromElement(PageElement pageElement) {
159 | WebElement element = driver.findElement(By.id(pageElement.getElementId()));
160 | return element.getText();
161 | }
162 |
163 | public boolean containsTextOnPage(String text) {
164 | return driver.getPageSource().contains(text);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/SpringAdapter.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter;
2 |
3 | import de.codecentric.psd.Worblehat;
4 | import io.cucumber.java.Before;
5 | import io.cucumber.spring.CucumberContextConfiguration;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.context.annotation.ComponentScan;
8 | import org.springframework.context.annotation.Configuration;
9 |
10 | @CucumberContextConfiguration
11 | @SpringBootTest(
12 | classes = {Worblehat.class, SpringAdapter.SpringConfig.class},
13 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
14 | public class SpringAdapter {
15 |
16 | @Before
17 | public void initializeSpringContext() {
18 | // just a marker method to make cucumber aware
19 |
20 | }
21 |
22 | @Configuration
23 | @ComponentScan(basePackages = "de.codecentric.psd.worblehat.acceptancetests")
24 | public static class SpringConfig {}
25 | }
26 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/wrapper/HtmlBook.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper;
2 |
3 | import java.time.LocalDate;
4 |
5 | public class HtmlBook {
6 |
7 | private String title;
8 | private String author;
9 | private Integer edition;
10 | private String isbn;
11 | private String yearOfPublication;
12 | private String borrower;
13 | private String description;
14 | private String cover;
15 | private LocalDate dueDate;
16 |
17 | public HtmlBook() {
18 | title = author = isbn = yearOfPublication = borrower = "";
19 | edition = 0;
20 | }
21 |
22 | public HtmlBook(
23 | String title,
24 | String author,
25 | String yearOfPublication,
26 | Integer edition,
27 | String isbn,
28 | String borrower) {
29 | this.title = title;
30 | this.author = author;
31 | this.edition = edition;
32 | this.isbn = isbn;
33 | this.yearOfPublication = yearOfPublication;
34 | this.borrower = borrower;
35 | }
36 |
37 | public String getTitle() {
38 | return title;
39 | }
40 |
41 | public void setTitle(String title) {
42 | this.title = title;
43 | }
44 |
45 | public String getAuthor() {
46 | return author;
47 | }
48 |
49 | public void setAuthor(String author) {
50 | this.author = author;
51 | }
52 |
53 | public Integer getEdition() {
54 | return edition;
55 | }
56 |
57 | public void setEdition(Integer edition) {
58 | this.edition = edition;
59 | }
60 |
61 | public String getIsbn() {
62 | return isbn;
63 | }
64 |
65 | public void setIsbn(String isbn) {
66 | this.isbn = isbn;
67 | }
68 |
69 | public String getYearOfPublication() {
70 | return yearOfPublication;
71 | }
72 |
73 | public void setYearOfPublication(String yearOfPublication) {
74 | this.yearOfPublication = yearOfPublication;
75 | }
76 |
77 | public String getBorrower() {
78 | return borrower;
79 | }
80 |
81 | public void setBorrower(String borrower) {
82 | this.borrower = borrower;
83 | }
84 |
85 | public String getDescription() {
86 | return description;
87 | }
88 |
89 | public void setDescription(final String description) {
90 | this.description = description;
91 | }
92 |
93 | public String getCover() {
94 | return cover;
95 | }
96 |
97 | public void setCover(String cover) {
98 | this.cover = cover;
99 | }
100 |
101 | public void setDueDate(LocalDate dueDate) {
102 | this.dueDate = dueDate;
103 | }
104 |
105 | public LocalDate getDueDate() {
106 | return dueDate;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/wrapper/HtmlBookList.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper;
2 |
3 | import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE;
4 |
5 | import java.time.LocalDate;
6 | import java.util.LinkedHashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import org.openqa.selenium.By;
10 | import org.openqa.selenium.WebElement;
11 |
12 | public class HtmlBookList {
13 | private List headers;
14 | private Map books;
15 |
16 | public HtmlBookList(WebElement table) {
17 |
18 | headers = table.findElements(By.cssSelector("thead tr th"));
19 |
20 | WebElement tbody = table.findElement(By.tagName("tbody"));
21 | extractValues(tbody);
22 | }
23 |
24 | private void extractValues(WebElement tbody) {
25 | books = new LinkedHashMap<>();
26 | for (WebElement row : tbody.findElements(By.tagName("tr"))) {
27 | List cells = row.findElements(By.tagName("td"));
28 |
29 | HtmlBook book = new HtmlBook();
30 | int currentColumn = 0;
31 | for (WebElement column : headers) {
32 | switch (column.getText()) {
33 | case "Title":
34 | book.setTitle(cells.get(currentColumn).getText());
35 | break;
36 | case "Author":
37 | book.setAuthor(cells.get(currentColumn).getText());
38 | break;
39 | case "Year":
40 | book.setYearOfPublication(cells.get(currentColumn).getText());
41 | break;
42 | case "Edition":
43 | book.setEdition(Integer.parseInt(cells.get(currentColumn).getText()));
44 | break;
45 | case "Borrower":
46 | book.setBorrower(cells.get(currentColumn).getText());
47 | break;
48 | case "ISBN":
49 | book.setIsbn(cells.get(currentColumn).getText());
50 | break;
51 | case "Description":
52 | book.setDescription(cells.get(currentColumn).getText());
53 | break;
54 | case "Cover":
55 | book.setCover(
56 | cells.get(currentColumn).findElement(By.tagName("img")).getAttribute("src"));
57 | break;
58 | case "Due Date":
59 | book.setDueDate(
60 | LocalDate.from(ISO_LOCAL_DATE.parse(cells.get(currentColumn).getText())));
61 | break;
62 | }
63 | currentColumn++;
64 | }
65 |
66 | books.put(book.getIsbn(), book);
67 | }
68 | }
69 |
70 | public int size() {
71 | return books.size();
72 | }
73 |
74 | public HtmlBook getBookByIsbn(String isbn) {
75 | return books.get(isbn);
76 | }
77 |
78 | public Map getHtmlBooks() {
79 | return books;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/wrapper/Page.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper;
2 |
3 | public enum Page {
4 | HOME("/"),
5 | BOOKLIST("bookList"),
6 | BORROWED_BOOK_LIST("borrowedBookList"),
7 | INSERTBOOKS("insertBooks"),
8 | BORROWBOOK("borrow"),
9 | RETURNBOOKS("returnAllBooks"),
10 | REMOVEBOOK("removeBook?isbn=%s"),
11 | BOOKDETAILS("bookDetails?isbn=%s");
12 |
13 | private String url;
14 |
15 | Page(String url) {
16 | this.url = url;
17 | }
18 |
19 | public String getUrl() {
20 | return url;
21 | }
22 |
23 | public String getUrl(String parameter) {
24 | return String.format(url, parameter);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/adapter/wrapper/PageElement.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper;
2 |
3 | public enum PageElement {
4 | ADD_BOOK_BUTTON("addBook"),
5 | BORROWED_BOOK_LIST("borrowedBookList"),
6 | BOOK_LIST("bookList"),
7 | BORROW_BOOK_BUTTON("borrowBook"),
8 | ISBN_ERROR("isbn.error"),
9 | EMAIL_ERROR("email.error"),
10 | AUTHOR_ERROR("author.error"),
11 | EDITION_ERROR("edition.error"),
12 | TITLE_ERROR("title.error"),
13 | YEAR_ERROR("yearOfPublication.error"),
14 | RETURN_ALL_BOOKS_BUTTON("returnAllBooks"),
15 | BORROWING_LIST("borrowingsList"),
16 | SHOW_BORROWED_BOOKS_BUTTON("showBorrowedBooks"),
17 | ERROR("error"),
18 | REMOVE_BOOK_BUTTON("removeBook");
19 |
20 | private String elementId;
21 |
22 | PageElement(String elementId) {
23 | this.elementId = elementId;
24 | }
25 |
26 | public static PageElement errorFor(String field) {
27 | switch (field) {
28 | case "isbn":
29 | return PageElement.ISBN_ERROR;
30 | case "edition":
31 | return PageElement.EDITION_ERROR;
32 | case "year":
33 | return PageElement.YEAR_ERROR;
34 | default:
35 | throw new IllegalArgumentException("Could not find error element for " + field);
36 | }
37 | }
38 |
39 | public String getElementId() {
40 | return elementId;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/StoryContext.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step;
2 |
3 | import io.cucumber.java.After;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 | import java.util.Map.Entry;
7 |
8 | /**
9 | * Kontext eines Szenarios. Intern wird ein ThreadLocal verwendet, um fuer jeden ausfuehrenden
10 | * Thread eine eigene Instanz zu halten. Dieses Verhalten ist ueber die Getter und Setter
11 | * transparent.
12 | */
13 | public class StoryContext {
14 |
15 | private static final ThreadLocal context = new ContextLocal();
16 | private Map keyvalues = new HashMap<>();
17 | private Map objects = new HashMap<>();
18 |
19 | public static StoryContext getContextForCurrentThread() {
20 | return context.get();
21 | }
22 |
23 | private StoryContext self() {
24 | return getContextForCurrentThread();
25 | }
26 |
27 | @After
28 | public void reset() {
29 | context.set(new StoryContext());
30 | }
31 |
32 | public void put(String key, String value) {
33 | self().keyvalues.put(key, value);
34 | }
35 |
36 | public String get(String key) {
37 | return self().keyvalues.get(key);
38 | }
39 |
40 | public void putObject(String key, Object value) {
41 | self().objects.put(key, value);
42 | }
43 |
44 | public Object getObject(String key) {
45 | return self().objects.get(key);
46 | }
47 |
48 | public Map getKeyValueMap() {
49 | return self().keyvalues;
50 | }
51 |
52 | public Map getObjectValueMap() {
53 | return self().objects;
54 | }
55 |
56 | public void updateWithMap(Map replacements) {
57 | for (Entry replacementEntry : replacements.entrySet()) {
58 | String key = replacementEntry.getKey();
59 | String value = replacementEntry.getValue();
60 | self().put(key, value);
61 | }
62 | }
63 |
64 | public void updateWithObjectMap(Map replacements) {
65 | for (Entry replacementEntry : replacements.entrySet()) {
66 | String key = replacementEntry.getKey();
67 | Object value = replacementEntry.getValue();
68 | self().putObject(key, value);
69 | }
70 | }
71 |
72 | public void updateWithContext(StoryContext context2) {
73 | updateWithMap(context2.getKeyValueMap());
74 | updateWithObjectMap(context2.getObjectValueMap());
75 | }
76 |
77 | static final class ContextLocal extends ThreadLocal {
78 | @Override
79 | protected StoryContext initialValue() {
80 | return new StoryContext();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/business/DemoBookFactory.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.business;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 |
5 | public class DemoBookFactory {
6 |
7 | private Book book;
8 |
9 | private DemoBookFactory() {
10 | this.book = new Book("A book title", "A book author", "1", "1234567890", 2013);
11 | }
12 |
13 | public static DemoBookFactory createDemoBook() {
14 | return new DemoBookFactory();
15 | }
16 |
17 | public DemoBookFactory withTitle(String title) {
18 | this.book.setTitle(title);
19 | return this;
20 | }
21 |
22 | public DemoBookFactory withAuthor(String author) {
23 | this.book.setAuthor(author);
24 | return this;
25 | }
26 |
27 | public DemoBookFactory withEdition(String edition) {
28 | this.book.setEdition(edition);
29 | return this;
30 | }
31 |
32 | public DemoBookFactory withISBN(String isbn) {
33 | this.book.setIsbn(isbn);
34 | return this;
35 | }
36 |
37 | public DemoBookFactory withYearOfPublication(String year) {
38 | this.book.setYearOfPublication(Integer.parseInt(year));
39 | return this;
40 | }
41 |
42 | public DemoBookFactory withYearOfPublication(int yearOfPublication) {
43 | this.book.setYearOfPublication(yearOfPublication);
44 | return this;
45 | }
46 |
47 | public Book build() {
48 | return book;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/business/Library.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.business;
2 |
3 | import com.google.common.base.Splitter;
4 | import de.codecentric.psd.worblehat.acceptancetests.step.StoryContext;
5 | import de.codecentric.psd.worblehat.domain.*;
6 | import io.cucumber.java.en.Given;
7 | import io.cucumber.java.en.Then;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.context.ApplicationContext;
10 |
11 | import java.time.LocalDate;
12 | import java.time.ZoneId;
13 | import java.util.List;
14 | import java.util.Set;
15 | import java.util.stream.Collectors;
16 |
17 | import static org.hamcrest.CoreMatchers.everyItem;
18 | import static org.hamcrest.CoreMatchers.is;
19 | import static org.hamcrest.MatcherAssert.assertThat;
20 | import static org.hamcrest.Matchers.hasProperty;
21 |
22 | public class Library {
23 |
24 | @Autowired(required = true)
25 | private final BookService bookService;
26 |
27 | @Autowired
28 | private BorrowingRepository borrowingRepository;
29 |
30 | @Autowired
31 | private BookRepository bookRepository;
32 |
33 | private final StoryContext storyContext;
34 |
35 | @Autowired
36 | public Library(ApplicationContext applicationContext, StoryContext storyContext) {
37 | this.bookService = applicationContext.getBean(BookService.class);
38 | this.storyContext = storyContext;
39 | }
40 |
41 | // *******************
42 | // *** G I V E N *****
43 | // *******************
44 |
45 | @Given("an empty library")
46 | public void emptyLibrary() {
47 | bookService.deleteAllBooks();
48 | }
49 |
50 | @Given("a library, containing (only one )book(s) with isbn(s) {string}")
51 | public void createLibraryWithSingleBookWithGivenIsbn(String isbns) {
52 | emptyLibrary();
53 | List newBooks = createNewBooksByISBNS(isbns);
54 | newBooks.stream().forEach(book -> storyContext.putObject("LAST_INSERTED_BOOK", book));
55 | storyContext.putObject("LAST_INSERTED_BOOKS", newBooks);
56 |
57 |
58 | // Book book = DemoBookFactory.createDemoBook().withISBN(isbn).build();
59 | // Optional createdBook =
60 | // bookService.createBook(
61 | // new BookParameter(
62 | // book.getTitle(),
63 | // book.getAuthor(),
64 | // book.getEdition(),
65 | // book.getIsbn(),
66 | // book.getYearOfPublication(),
67 | // book.getDescription()));
68 | // createdBook.ifPresent(b -> storyContext.putObject("LAST_INSERTED_BOOK", b));
69 | }
70 |
71 |
72 | @Given("{string} has borrowed book(s) {string}")
73 | public void borrowerHasBorrowerdBooks(String borrower, String isbns) {
74 | has_borrowed_books_on(borrower, isbns, LocalDate.now());
75 | }
76 |
77 | @Given("{string} has borrowed books {string} on {date}")
78 | public void has_borrowed_books_on(String borrower, String isbns, LocalDate borrowDate) {
79 | List books = (List) storyContext.getObject("LAST_INSERTED_BOOKS");
80 | Splitter.on(" ").omitEmptyStrings().splitToList(isbns).stream().forEach(isbn -> {
81 | Book bookToBeBorrowed = books.stream()
82 | .filter(book -> book.getIsbn().equals(isbn))
83 | .findFirst().orElseThrow();
84 |
85 | LocalDate date = borrowDate.atStartOfDay(ZoneId.systemDefault()).toLocalDate();
86 |
87 | Borrowing newBorrowing = new Borrowing(bookToBeBorrowed, borrower, date);
88 | borrowingRepository.save(newBorrowing);
89 |
90 | bookToBeBorrowed.setBorrowing(newBorrowing);
91 | bookRepository.save(bookToBeBorrowed);
92 | });
93 | }
94 |
95 | private List createNewBooksByISBNS(String isbns) {
96 | List books = Splitter.on(" ").omitEmptyStrings().splitToList(isbns).stream().map(isbn -> {
97 | Book book = DemoBookFactory.createDemoBook().withISBN(isbn).build();
98 | Book newBook =
99 | bookService
100 | .createBook(
101 | new BookParameter(
102 | book.getTitle(),
103 | book.getAuthor(),
104 | book.getEdition(),
105 | book.getIsbn(),
106 | book.getYearOfPublication(),
107 | book.getDescription()))
108 | .orElseThrow(IllegalStateException::new);
109 | return newBook;
110 |
111 | }).collect(Collectors.toList());
112 | return books;
113 | }
114 |
115 | // *****************
116 | // *** W H E N *****
117 | // *****************
118 |
119 | // *****************
120 | // *** T H E N *****
121 | // *****************
122 |
123 | @Then("the library contains {long} books")
124 | public void shouldContainNumberOfBooks(long books) {
125 | assertThat(bookRepository.count(), is(books));
126 | }
127 |
128 | @Then("the library contains {int} book(s) with {string}")
129 | public void shouldContainNumberOfBooksWithISBN(int books, String isbn) {
130 | if (isbn.isEmpty()) return;
131 | assertThat(bookRepository.findByIsbn(isbn).size(), is(books));
132 | }
133 |
134 | @Then("the library still contains all borrowed books {string}")
135 | public void shouldContainOneCopyOf(String isbn) {
136 | if (isbn.isEmpty()) return;
137 | assertThat(bookRepository.findByIsbn(isbn).size(), is(1));
138 | }
139 |
140 | @Then("the new book {string} be added")
141 | public void shouldNotHaveCreatedANewCopy(String can) {
142 | Book lastInsertedBook = (Book) storyContext.getObject("LAST_INSERTED_BOOK");
143 | int numberOfCopies = "CAN".equals(can) ? 2 : 1;
144 | assertNumberOfCopies(lastInsertedBook.getIsbn(), numberOfCopies);
145 | }
146 |
147 | private void assertNumberOfCopies(String isbn, int nrOfCopies) {
148 | Set books = bookService.findBooksByIsbn(isbn);
149 | assertThat(books.size(), is(nrOfCopies));
150 | assertThat(books, everyItem(hasProperty("isbn", is(isbn))));
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/BookDetailsPage.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
4 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
5 | import de.codecentric.psd.worblehat.acceptancetests.step.StoryContext;
6 | import de.codecentric.psd.worblehat.domain.Book;
7 | import de.codecentric.psd.worblehat.domain.BookService;
8 | import io.cucumber.java.en.Then;
9 | import io.cucumber.java.en.When;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 |
12 | import java.util.Optional;
13 | import java.util.Set;
14 |
15 | import static org.hamcrest.CoreMatchers.is;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 | import static org.hamcrest.Matchers.greaterThanOrEqualTo;
18 | import static org.hamcrest.Matchers.hasSize;
19 |
20 | public class BookDetailsPage {
21 |
22 | private SeleniumAdapter seleniumAdapter;
23 | private StoryContext storyContext;
24 | private BookService bookService;
25 |
26 | @Autowired
27 | public BookDetailsPage(
28 | SeleniumAdapter seleniumAdapter, StoryContext storyContext, BookService bookService) {
29 | this.seleniumAdapter = seleniumAdapter;
30 | this.storyContext = storyContext;
31 | this.bookService = bookService;
32 | }
33 |
34 | @When("I navigate to the detail page of the book with the isbn {string}")
35 | public void navigateToDetailPage(String isbn) {
36 | seleniumAdapter.clickOnPageElementByClassName("detailsLink-" + isbn);
37 | storyContext.put("LAST_BROWSED_BOOK_DETAILS", isbn);
38 | }
39 |
40 | @When("a librarian removes book(s) {string}")
41 | public void a_librarian_removes_book(String isbn) {
42 | seleniumAdapter.gotoPageWithParameter(Page.REMOVEBOOK, isbn);
43 | // seleniumAdapter.clickOnPageElementById(PageElement.REMOVE_BOOK_BUTTON);
44 | }
45 |
46 | @Then("I can see all book details for that book")
47 | public void allBookDetailsVisible() {
48 | String isbn = storyContext.get("LAST_BROWSED_BOOK_DETAILS");
49 | Set books = bookService.findBooksByIsbn(isbn);
50 | assertThat(books, hasSize(greaterThanOrEqualTo(1)));
51 | Book book = books.iterator().next();
52 | String description = book.getDescription();
53 | assertThat(seleniumAdapter.containsTextOnPage(book.getIsbn()), is(true));
54 | assertThat(seleniumAdapter.containsTextOnPage(book.getAuthor()), is(true));
55 | assertThat(seleniumAdapter.containsTextOnPage(book.getTitle()), is(true));
56 | assertThat(seleniumAdapter.containsTextOnPage(book.getEdition()), is(true));
57 | assertThat(
58 | seleniumAdapter.containsTextOnPage(String.valueOf(book.getYearOfPublication())), is(true));
59 | Optional.ofNullable(description)
60 | .ifPresent(desc -> assertThat(seleniumAdapter.containsTextOnPage(description), is(true)));
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/BookList.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import static org.hamcrest.MatcherAssert.assertThat;
4 | import static org.hamcrest.Matchers.*;
5 |
6 | import com.google.common.base.Splitter;
7 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
8 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.HtmlBook;
9 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.HtmlBookList;
10 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
11 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
12 | import de.codecentric.psd.worblehat.acceptancetests.step.StoryContext;
13 | import de.codecentric.psd.worblehat.domain.Book;
14 | import io.cucumber.java.en.Given;
15 | import io.cucumber.java.en.Then;
16 | import java.util.*;
17 | import org.springframework.beans.factory.annotation.Autowired;
18 |
19 | public class BookList {
20 |
21 | private SeleniumAdapter seleniumAdapter;
22 |
23 | @Autowired public StoryContext storyContext;
24 |
25 | @Autowired
26 | public BookList(SeleniumAdapter seleniumAdapter) {
27 | this.seleniumAdapter = seleniumAdapter;
28 | }
29 |
30 | @Given("I browse the list of all books")
31 | public void browseBookList() {
32 | seleniumAdapter.gotoPage(Page.BOOKLIST);
33 | }
34 |
35 | @Then("the booklist contains a book with {string}, {string}, {string}, {int} and {string}")
36 | public void bookListContainsRowWithValues(
37 | final String title,
38 | final String author,
39 | final String year,
40 | final Integer edition,
41 | final String isbn) {
42 | seleniumAdapter.gotoPage(Page.BOOKLIST);
43 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
44 | HtmlBook htmlBook = htmlBookList.getBookByIsbn(isbn);
45 | assertThat(title, is(htmlBook.getTitle()));
46 | assertThat(author, is(htmlBook.getAuthor()));
47 | assertThat(year, is(htmlBook.getYearOfPublication()));
48 | assertThat(edition, is(htmlBook.getEdition()));
49 | assertThat(isbn, is(htmlBook.getIsbn()));
50 | }
51 |
52 | @Then("the booklist contains a book with all the properties from the last inserted book")
53 | public void justAsLastInserted() {
54 | Book lastInserted = (Book) storyContext.getObject("LAST_INSERTED_BOOK");
55 | seleniumAdapter.gotoPage(Page.BOOKLIST);
56 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
57 | HtmlBook htmlBook = htmlBookList.getBookByIsbn(lastInserted.getIsbn());
58 | assertThat(lastInserted.getTitle(), containsString(htmlBook.getTitle()));
59 | assertThat(lastInserted.getAuthor(), containsString(htmlBook.getAuthor()));
60 | assertThat(
61 | lastInserted.getYearOfPublication(), is(Integer.parseInt(htmlBook.getYearOfPublication())));
62 | assertThat(lastInserted.getEdition(), containsString(htmlBook.getEdition().toString()));
63 | assertThat(lastInserted.getDescription(), containsString(htmlBook.getDescription()));
64 | }
65 |
66 | @Then(
67 | "the booklist contains a book with {string}, {string}, {string}, {int}, {string}, and {string}")
68 | public void bookListContainsRowWithValues(
69 | final String isbn,
70 | final String title,
71 | final String author,
72 | final Integer edition,
73 | final String year,
74 | final String description) {
75 | seleniumAdapter.gotoPage(Page.BOOKLIST);
76 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
77 | HtmlBook htmlBook = htmlBookList.getBookByIsbn(isbn.trim());
78 | assertThat(title, containsString(htmlBook.getTitle()));
79 | assertThat(author, containsString(htmlBook.getAuthor()));
80 | assertThat(year, containsString(htmlBook.getYearOfPublication()));
81 | assertThat(edition, is(htmlBook.getEdition()));
82 | assertThat(isbn, containsString(htmlBook.getIsbn()));
83 | assertThat(description, containsString(htmlBook.getDescription()));
84 | }
85 |
86 | @Then("The library contains no books")
87 | public void libraryIsEmpty() {
88 | seleniumAdapter.gotoPage(Page.BOOKLIST);
89 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
90 | assertThat(htmlBookList.size(), is(0));
91 | }
92 |
93 | @Then("the booklist lists {string} as borrower for the book with isbn {string}")
94 | public void bookListHasBorrowerForBookWithIsbn(final String borrower, final String isbn) {
95 | seleniumAdapter.gotoPage(Page.BOOKLIST);
96 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
97 | HtmlBook htmlBook = htmlBookList.getBookByIsbn(isbn);
98 | assertThat(htmlBook.getBorrower(), is(borrower));
99 | }
100 |
101 | @Then("book(s) {string} is/are {string} by {string}")
102 | public void booksAreNotBorrowedByBorrower1(String isbns, String borrowStatus, String borrower) {
103 | seleniumAdapter.gotoPage(Page.BOOKLIST);
104 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
105 |
106 | long booksInWrongBorrowingState =
107 | Splitter.on(" ").omitEmptyStrings().splitToList(isbns).stream()
108 | .filter(
109 | isbn -> {
110 | String actualBorrower = htmlBookList.getBookByIsbn(isbn).getBorrower();
111 | return !borrowStatus.contains("not borrowed") != borrower.equals(actualBorrower);
112 | })
113 | .count();
114 |
115 | assertThat(booksInWrongBorrowingState, is(0L));
116 | }
117 |
118 | @Then("for every book the booklist contains a cover")
119 | public void checkCover() {
120 | seleniumAdapter.gotoPage(Page.BOOKLIST);
121 | HtmlBookList htmlBookList = seleniumAdapter.getTableContent(PageElement.BOOK_LIST);
122 | Collection books = htmlBookList.getHtmlBooks().values();
123 | for (HtmlBook book : books) {
124 | assertThat(book.getCover(), containsString(book.getIsbn()));
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/BorrowBook.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
7 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
8 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
9 | import io.cucumber.java.en.Then;
10 | import io.cucumber.java.en.When;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 |
13 | public class BorrowBook {
14 |
15 | private SeleniumAdapter seleniumAdapter;
16 |
17 | @Autowired
18 | public BorrowBook(SeleniumAdapter seleniumAdapter) {
19 | this.seleniumAdapter = seleniumAdapter;
20 | }
21 |
22 | // *******************
23 | // *** G I V E N *****
24 | // *******************
25 |
26 | // *****************
27 | // *** W H E N *****s
28 | // *****************
29 |
30 | @When("{string} borrows the book {string}")
31 | public void whenUseruserBorrowsTheBookisbn(String user, String isbn) {
32 | seleniumAdapter.gotoPage(Page.BORROWBOOK);
33 | seleniumAdapter.typeIntoField("email", user);
34 | seleniumAdapter.typeIntoField("isbn", isbn);
35 | seleniumAdapter.clickOnPageElementById(PageElement.BORROW_BOOK_BUTTON);
36 | }
37 |
38 | // *****************
39 | // *** T H E N *****
40 | // *****************
41 |
42 | @Then(
43 | "there's an error {string}, when {string} tries to borrow the book with isbn {string} again")
44 | public void whenBorrowerBorrowsBorrowedBookShowErrorMessage(
45 | String message, String borrower, String isbn) {
46 | seleniumAdapter.gotoPage(Page.BORROWBOOK);
47 | seleniumAdapter.typeIntoField("email", borrower);
48 | seleniumAdapter.typeIntoField("isbn", isbn);
49 | seleniumAdapter.clickOnPageElementById(PageElement.BORROW_BOOK_BUTTON);
50 | String errorMessage = seleniumAdapter.getTextFromElement(PageElement.ISBN_ERROR);
51 | assertThat(errorMessage, is(message));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/InsertBook.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import static org.hamcrest.MatcherAssert.assertThat;
4 | import static org.hamcrest.Matchers.notNullValue;
5 |
6 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
7 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
8 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
9 | import de.codecentric.psd.worblehat.acceptancetests.step.StoryContext;
10 | import de.codecentric.psd.worblehat.domain.Book;
11 | import de.codecentric.psd.worblehat.domain.BookParameter;
12 | import io.cucumber.java.en.Then;
13 | import io.cucumber.java.en.When;
14 | import java.util.Optional;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 |
17 | public class InsertBook {
18 |
19 | private SeleniumAdapter seleniumAdapter;
20 |
21 | @Autowired public StoryContext storyContext;
22 |
23 | @Autowired
24 | public InsertBook(SeleniumAdapter seleniumAdapter, StoryContext storyContext) {
25 | this.seleniumAdapter = seleniumAdapter;
26 | this.storyContext = storyContext;
27 | }
28 |
29 | // *******************
30 | // *** G I V E N *****
31 | // *******************
32 |
33 | // *****************
34 | // *** W H E N *****
35 | // *****************
36 |
37 | @When("a librarian adds a book with {string}, {string}, {string}, {int}, and {string}")
38 | public void whenABookWithISBNisbnIsAdded(
39 | String isbn, String title, String author, Integer edition, String year) {
40 | insertAndSubmitBook(isbn, title, author, edition, year, "");
41 | }
42 |
43 | @When("a librarian adds a book with {string}, {string}, {string}, {int}, {string}, and {string}")
44 | public void whenABookIsAdded(
45 | String isbn, String title, String author, Integer edition, String year, String description) {
46 | insertAndSubmitBook(isbn, title, author, edition, year, description);
47 | }
48 |
49 | @When("a librarian tries to add a similar book with different {string}, {string} and {int}")
50 | public void whenASimilarBookIsAdded(String title, String author, Integer edition) {
51 | Book lastInsertedBook = (Book) storyContext.getObject("LAST_INSERTED_BOOK");
52 | insertAndSubmitBook(
53 | lastInsertedBook.getIsbn(),
54 | title,
55 | author,
56 | edition,
57 | String.valueOf(lastInsertedBook.getYearOfPublication()),
58 | lastInsertedBook.getDescription());
59 | }
60 |
61 | @When("a librarian tries to add a similar book with same title, author and edition")
62 | public void whenASimilarBookIsAdded() {
63 | Book lastInsertedBook = (Book) storyContext.getObject("LAST_INSERTED_BOOK");
64 | insertAndSubmitBook(
65 | lastInsertedBook.getIsbn(),
66 | lastInsertedBook.getTitle(),
67 | lastInsertedBook.getAuthor(),
68 | Integer.parseInt(lastInsertedBook.getEdition()),
69 | String.valueOf(lastInsertedBook.getYearOfPublication()),
70 | lastInsertedBook.getDescription());
71 | }
72 |
73 | // *****************
74 | // *** T H E N *****
75 | // *****************
76 | @Then("the page contains error message for field {string}")
77 | public void pageContainsErrorMessage(String field) {
78 | String errorMessage = seleniumAdapter.getTextFromElement(PageElement.errorFor(field));
79 | assertThat(errorMessage, notNullValue());
80 | }
81 |
82 | // *****************
83 | // *** U T I L *****
84 | // *****************
85 |
86 | private void insertAndSubmitBook(
87 | String isbn, String title, String author, Integer edition, String year, String description) {
88 | seleniumAdapter.gotoPage(Page.INSERTBOOKS);
89 | fillInsertBookForm(title, author, edition, isbn, year, description);
90 | seleniumAdapter.clickOnPageElementById(PageElement.ADD_BOOK_BUTTON);
91 | storyContext.putObject(
92 | "LAST_INSERTED_BOOK",
93 | new Book(
94 | new BookParameter(
95 | title,
96 | author,
97 | edition.toString(),
98 | isbn,
99 | Integer.parseInt(year.trim()),
100 | description)));
101 | }
102 |
103 | private void fillInsertBookForm(
104 | String title, String author, Integer edition, String isbn, String year, String description) {
105 | seleniumAdapter.typeIntoField("title", title);
106 | seleniumAdapter.typeIntoField("edition", edition.toString());
107 | seleniumAdapter.typeIntoField("isbn", isbn);
108 | seleniumAdapter.typeIntoField("author", author);
109 | seleniumAdapter.typeIntoField("yearOfPublication", year);
110 | Optional.ofNullable(description)
111 | .ifPresent(desc -> seleniumAdapter.typeIntoField("description", description));
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/ListBorrowedBooksPage.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
6 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.HtmlBook;
7 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.HtmlBookList;
8 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
9 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
10 | import de.codecentric.psd.worblehat.acceptancetests.step.StoryContext;
11 | import io.cucumber.java.en.Then;
12 | import io.cucumber.java.en.When;
13 | import java.time.LocalDate;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 |
16 | public class ListBorrowedBooksPage {
17 |
18 | private SeleniumAdapter seleniumAdapter;
19 | private StoryContext storyContext;
20 |
21 | @Autowired
22 | public ListBorrowedBooksPage(SeleniumAdapter seleniumAdapter, StoryContext storyContext) {
23 | this.seleniumAdapter = seleniumAdapter;
24 | this.storyContext = storyContext;
25 | }
26 |
27 | @When("{string} requests list of borrowed books")
28 | public void requests_list_of_borrowed_books(String borrower) {
29 | seleniumAdapter.gotoPage(Page.BORROWED_BOOK_LIST);
30 | seleniumAdapter.typeIntoField("email", borrower);
31 | seleniumAdapter.clickOnPageElementById(PageElement.SHOW_BORROWED_BOOKS_BUTTON);
32 | }
33 |
34 | @Then("the list shows the books {string} with due date {date}")
35 | public void the_list_shows_the_books(String isbns, LocalDate dueDate) {
36 | HtmlBookList tableContent = seleniumAdapter.getTableContent(PageElement.BORROWED_BOOK_LIST);
37 | HtmlBook bookByIsbn = tableContent.getBookByIsbn(isbns);
38 |
39 | assertThat(bookByIsbn).isNotNull();
40 | assertThat(bookByIsbn)
41 | .hasFieldOrPropertyWithValue("dueDate", dueDate)
42 | .hasFieldOrPropertyWithValue("isbn", isbns);
43 | }
44 |
45 | @Then("the list is sorted by dueDate in ascending order")
46 | public void the_list_of_borrowed_books_for_lists_books_sorted_by_due_date_with_the_next_book_to_return_on_top() {
47 | HtmlBookList listOfBorrowings = seleniumAdapter.getTableContent(PageElement.BORROWED_BOOK_LIST);
48 | listOfBorrowings.getHtmlBooks().values().stream().reduce( (a, b) -> {
49 | if (a.getDueDate().compareTo(b.getDueDate()) > 0 ) throw new IllegalStateException("The Books " + a + " and " + b + " are not ordered correctly.");
50 | return b;
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/java/de/codecentric/psd/worblehat/acceptancetests/step/page/ReturnAllBooks.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.acceptancetests.step.page;
2 |
3 | import de.codecentric.psd.worblehat.acceptancetests.adapter.SeleniumAdapter;
4 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.Page;
5 | import de.codecentric.psd.worblehat.acceptancetests.adapter.wrapper.PageElement;
6 | import io.cucumber.java.en.When;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 |
9 | public class ReturnAllBooks {
10 | private final SeleniumAdapter seleniumAdapter;
11 |
12 | @Autowired
13 | public ReturnAllBooks(SeleniumAdapter seleniumAdapter) {
14 | this.seleniumAdapter = seleniumAdapter;
15 | }
16 |
17 | // *****************
18 | // *** W H E N *****
19 | // *****************
20 |
21 | @When("{string} returns the/all book(s)")
22 | public void whenUseruserReturnsAllHisBooks(String borrower1) {
23 | seleniumAdapter.gotoPage(Page.RETURNBOOKS);
24 | seleniumAdapter.typeIntoField("emailAddress", borrower1);
25 | seleniumAdapter.clickOnPageElementById(PageElement.RETURN_ALL_BOOKS_BUTTON);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:tc:postgresql:9.6.8://foo/bar
2 | spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
3 |
4 | spring.liquibase.change-log=classpath:master.xml
5 | spring.liquibase.drop-first=true
6 |
7 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
8 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/cucumber.properties:
--------------------------------------------------------------------------------
1 | cucumber.publish.quiet=true
2 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Add Book.feature:
--------------------------------------------------------------------------------
1 | Feature: Adding a new book to the library
2 |
3 | Scenario Outline: Adding a new book
4 |
5 | Given an empty library
6 | When a librarian adds a book with "", "", "", , "", and ""
7 | Then the booklist contains a book with all the properties from the last inserted book
8 | And for every book the booklist contains a cover
9 |
10 | Examples:
11 |
12 | | isbn | author | title | edition | year | description |
13 | | 0552131075 | Terry Pratchett | Sourcery | 1 | 1989 | |
14 | | 0552131075 | Terry Pratchett | Sourcery | 1 | 1989 | A description |
15 | | 9783827317247 | Andreas Thiel | Komponentenmodelle | 1 | 2000 | Komponentenmodelle FTW |
16 |
17 |
18 | Scenario Outline: Different books must have different properties (ISBN, title, author, edition)
19 |
20 | Given a library, containing only one book with isbn ""
21 | When a librarian tries to add a similar book with different "", "" and
22 | Then the new book "CAN NOT" be added
23 |
24 | Examples:
25 |
26 | | isbn | author | edition | title |
27 | | 0552131075 | Jerry Pratchett | 1 | Sourcery |
28 | | 0552131075 | Terry Pratchett | 1 | Mastery |
29 | | 0552131075 | Terry Pratchett | 2 | Sourcery |
30 |
31 | Scenario: Multiple copies of the same book must share common properties (ISBN, title, author, edition)
32 |
33 | Given a library, containing only one book with isbn "0552131075"
34 | When a librarian tries to add a similar book with same title, author and edition
35 | Then the new book "CAN" be added
36 | And for every book the booklist contains a cover
37 |
38 |
39 | Scenario: Whitespace is trimmed on book creation
40 |
41 | Given an empty library
42 | # Note the whitespace at the end of parameters in the following step
43 | When a librarian adds a book with " 9783827317247 ", "Komponentenmodelle ", "Andreas Thiel ", 1, "2000 ", and "Komponentenmodelle FTW "
44 | Then the booklist contains a book with "9783827317247", "Komponentenmodelle", "Andreas Thiel", 1, "2000", and "Komponentenmodelle FTW"
45 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/All Borrow Books.feature:
--------------------------------------------------------------------------------
1 | Feature: Show borrowed books from a single user
2 |
3 | @Focus
4 | Scenario Outline: Show borrowed books
5 |
6 | Given a library, containing books with isbns " "
7 | And "" has borrowed books "" on
8 | And "" has borrowed books "" on
9 |
10 | When "" requests list of borrowed books
11 |
12 | Then the list shows the books "" with due date
13 | And the list shows the books "" with due date
14 | And the list is sorted by dueDate in ascending order
15 |
16 | Examples:
17 |
18 | | borrower | isbns | borrowDate | dueDate | more_isbns | anotherBorrowDate | anotherDueDate |
19 | | sandra@worblehat.net | 123456789X | 2020-10-01 | 2020-10-29 | 342314162X | 2020-11-01 | 2020-11-29 |
20 | | sandra@worblehat.net | 123456789X | 2020-11-01 | 2020-11-29 | 342314162X | 2020-10-01 | 2020-10-29 |
21 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Book Details.feature:
--------------------------------------------------------------------------------
1 | Feature: Using a book's details page
2 |
3 | Scenario: Details page is available
4 |
5 | Given a library, containing only one book with isbn "0552131075"
6 | And I browse the list of all books
7 | When I navigate to the detail page of the book with the isbn "0552131075"
8 | Then I can see all book details for that book
9 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Book Not Added.feature:
--------------------------------------------------------------------------------
1 | Feature: Testing for basic validation when adding new books
2 |
3 | Scenario Outline: Providing invalid input data for new books
4 |
5 | Given an empty library
6 |
7 | When a librarian adds a book with "", "", "", , and ""
8 |
9 | Then the page contains error message for field ""
10 | And The library contains no books
11 |
12 | Examples:
13 |
14 | | isbn | author | title | edition | year | field |
15 | | 0XXXXXXXX5 | Terry Pratchett | Sourcery | 1 | 1989 | isbn |
16 | | 0552131075 | Terry Pratchett | Sourcery | 1 | 1 | year |
17 |
18 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Borrow Book.feature:
--------------------------------------------------------------------------------
1 | Feature: Borrowing borrowed and available books
2 |
3 | Scenario Outline: Borrowed books cannot be borrowed again
4 |
5 | Given a library, containing only one book with isbn ""
6 |
7 | When "" borrows the book ""
8 | Then the booklist lists "" as borrower for the book with isbn ""
9 |
10 | And there's an error "", when "" tries to borrow the book with isbn "" again
11 |
12 | Examples:
13 |
14 | | isbn | user | message |
15 | | 0552131075 | user@test.com | The book is already borrowed. |
16 |
17 | Scenario: Borrow ignores whitespaces
18 |
19 | Given a library, containing only one book with isbn "0552131075"
20 | When " test@me.com " borrows the book " 0552131075"
21 | Then the booklist lists "test@me.com" as borrower for the book with isbn "0552131075"
22 |
23 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Remove Book.feature:
--------------------------------------------------------------------------------
1 | Feature: Remove a copy of a book from the library
2 |
3 | @Focus
4 | Scenario Outline: Remove a book
5 |
6 | Given a library, containing books with isbn "123456789X "
7 | And "user1@discworld.dw" has borrowed books ""
8 |
9 | When a librarian removes book ""
10 |
11 | Then the library contains books with "123456789X"
12 | And the library still contains all borrowed books ""
13 |
14 | Examples:
15 |
16 | | user1_isbns | removed_isbns | number_of_books |
17 | | | 123456789X | 0 |
18 | | 9999999999 | 123456789X | 0 |
19 | | 123456789X | 123456789X | 1 |
20 | | 9999999999 | 9999999999 | 1 |
21 | | 9999999999 | 8888888888 | 1 |
22 |
--------------------------------------------------------------------------------
/worblehat-acceptancetests/src/test/resources/de/codecentric/psd/worblehat/features/book/Return All Books.feature:
--------------------------------------------------------------------------------
1 | Feature: Returning - giving back - borrowed books
2 |
3 | Scenario Outline: Returning all books at once
4 |
5 | Given a library, containing books with isbns " "
6 | And "" has borrowed books ""
7 | And "" has borrowed books ""
8 | When "" returns all books
9 | Then books "" are "not borrowed anymore" by ""
10 | But books "" are "still borrowed" by ""
11 |
12 | Examples:
13 |
14 | | borrower1 | isbns1 | borrower2 | isbns2 |
15 | | user1@dings.com | 0321293533 | | |
16 | | user1@dings.com | 0321293533 | user2@dings.com | 1234567962 |
17 | | user1@dings.com | 0321293533 1234567962 | | |
18 | | user1@dings.com | 0321293533 1234567962 | user2@dings.com | 7784484156 1126108624 |
19 |
20 | Scenario: Returning books ignores whitespaces
21 | Given a library, containing books with isbns "1234567962"
22 | And "test@me.com" has borrowed book "1234567962"
23 | When " test@me.com " returns the book
24 | Then book "1234567962" is "not borrowed anymore" by "test@me.com"
25 |
--------------------------------------------------------------------------------
/worblehat-domain/.checkstyle:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/worblehat-domain/.eclipse-pmd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/worblehat-domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `java-library`
3 | }
4 |
5 | repositories {
6 | mavenCentral()
7 | }
8 |
9 | dependencies {
10 | implementation(platform("org.springframework.boot:spring-boot-dependencies:2.6.6"))
11 | implementation("org.springframework.boot:spring-boot-starter-data-jpa")
12 | runtimeOnly("org.postgresql:postgresql")
13 | runtimeOnly("org.liquibase:liquibase-core")
14 |
15 | implementation("com.google.code.findbugs:jsr305:3.0.2")
16 | implementation("com.google.guava:guava:30.1.1-jre")
17 |
18 | compileOnly("org.projectlombok:lombok:1.18.32")
19 | annotationProcessor("org.projectlombok:lombok:1.18.32")
20 |
21 | testImplementation("org.mockito:mockito-core")
22 | testImplementation("com.github.npathai:hamcrest-optional:2.0.0")
23 | testImplementation("org.junit.jupiter:junit-jupiter-api")
24 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
25 | }
26 |
27 | tasks {
28 | test {
29 | useJUnitPlatform()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/worblehat-domain/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | de.codecentric.psd
6 | worblehat
7 | 1.4.0-SNAPSHOT
8 | ../
9 |
10 |
11 | worblehat-domain
12 | Worblehat Domain-Model
13 |
14 |
15 |
16 |
17 | org.springframework.boot
18 | spring-boot-starter-data-jpa
19 |
20 |
21 | org.mockito
22 | mockito-core
23 | test
24 |
25 |
26 | org.postgresql
27 | postgresql
28 |
29 |
30 | org.liquibase
31 | liquibase-core
32 |
33 |
34 | com.google.code.findbugs
35 | jsr305
36 | 3.0.2
37 |
38 |
39 | com.google.guava
40 | guava
41 | 30.1.1-jre
42 |
43 |
44 | com.github.npathai
45 | hamcrest-optional
46 | 2.0.0
47 | test
48 |
49 |
50 | org.junit.jupiter
51 | junit-jupiter-engine
52 | test
53 |
54 |
55 | org.projectlombok
56 | lombok
57 | provided
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.liquibase
65 | liquibase-maven-plugin
66 |
67 | master.xml
68 | com.mysql.jdbc.Driver
69 | ${psd.dbserver.url}
70 | ${psd.dbserver.username}
71 | ${psd.dbserver.password}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/Book.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import lombok.*;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.time.LocalDate;
8 |
9 | @Entity
10 | @Data
11 | @NoArgsConstructor
12 | @RequiredArgsConstructor
13 | public class Book implements Serializable {
14 |
15 | private static final long serialVersionUID = 1L;
16 |
17 | @Id
18 | @GeneratedValue(strategy = GenerationType.IDENTITY)
19 | private long id;
20 |
21 | @NonNull private String title;
22 |
23 | @NonNull private String author;
24 |
25 | @NonNull private String edition;
26 |
27 | // TODO: convert String to an ISBN class, that ensures a valid ISBN
28 | @NonNull private String isbn;
29 |
30 | @NonNull private Integer yearOfPublication;
31 |
32 | @OneToOne(mappedBy = "borrowedBook", orphanRemoval = true)
33 | @ToString.Exclude
34 | @EqualsAndHashCode.Exclude
35 | private Borrowing borrowing;
36 |
37 | @Column(columnDefinition = "TEXT")
38 | private String description;
39 |
40 | public Book(BookParameter bookParameter) {
41 | this(
42 | bookParameter.getTitle(),
43 | bookParameter.getAuthor(),
44 | bookParameter.getEdition(),
45 | bookParameter.getIsbn(),
46 | bookParameter.getYearOfPublication());
47 | this.description = bookParameter.getDescription();
48 | }
49 |
50 | Book(final Book book) {
51 | this(book.title, book.author, book.edition, book.isbn, book.yearOfPublication);
52 | this.description = book.description;
53 | }
54 |
55 | boolean isSameCopy(@NonNull Book book) {
56 | return getTitle().equals(book.title)
57 | && getAuthor().equals(book.author)
58 | && getEdition().equals(book.edition);
59 | }
60 |
61 | public void borrowNowByBorrower(String borrowerEmailAddress) {
62 | if (borrowing == null) {
63 | this.borrowing = new Borrowing(this, borrowerEmailAddress, LocalDate.now());
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/BookParameter.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import lombok.Data;
4 | import lombok.NonNull;
5 | import lombok.RequiredArgsConstructor;
6 |
7 | @Data
8 | @RequiredArgsConstructor
9 | public class BookParameter {
10 |
11 | @NonNull private final String title;
12 |
13 | @NonNull private final String author;
14 |
15 | @NonNull private final String edition;
16 |
17 | @NonNull private final String isbn;
18 |
19 | private final int yearOfPublication;
20 |
21 | private String description = "";
22 |
23 | public BookParameter(
24 | String title,
25 | String author,
26 | String edition,
27 | String isbn,
28 | int yearOfPublication,
29 | String description) {
30 | this(title, author, edition, isbn, yearOfPublication);
31 | this.description = description;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/BookRepository.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 | import java.util.Set;
6 | import org.springframework.data.jpa.repository.JpaRepository;
7 |
8 | public interface BookRepository extends JpaRepository {
9 |
10 | List findAllByOrderByTitle();
11 |
12 | Set findByIsbn(String isbn);
13 |
14 | Optional findTopByIsbn(String isbn);
15 |
16 | Set findByAuthor(String author);
17 |
18 | Set findByEdition(String edition);
19 | }
20 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/BookService.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 | import java.util.Set;
6 |
7 | /** The interface of the domain service for books. */
8 | public interface BookService {
9 |
10 | void returnAllBooksByBorrower(String string);
11 |
12 | Optional borrowBook(String isbn, String borrower);
13 |
14 | Set findBooksByIsbn(String isbn);
15 |
16 | List findAllBooks();
17 |
18 | Optional createBook(BookParameter bookParameter);
19 |
20 | boolean bookExists(String isbn);
21 |
22 | void deleteAllBooks();
23 |
24 | List findAllBorrowingsByEmailAddress(String emailAddress);
25 |
26 | void removeBook(String isbn);
27 | }
28 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/Borrowing.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import lombok.Data;
4 | import lombok.NoArgsConstructor;
5 | import lombok.NonNull;
6 | import lombok.RequiredArgsConstructor;
7 |
8 | import javax.persistence.*;
9 | import java.io.Serializable;
10 | import java.time.LocalDate;
11 |
12 | @Entity
13 | @Data
14 | @NoArgsConstructor
15 | @RequiredArgsConstructor
16 | public class Borrowing implements Serializable {
17 |
18 | private static final long serialVersionUID = 1L;
19 |
20 | @Id
21 | @GeneratedValue(strategy = GenerationType.IDENTITY)
22 | private long id;
23 |
24 | @NonNull @OneToOne private Book borrowedBook;
25 |
26 | @NonNull private String borrowerEmailAddress;
27 |
28 | @NonNull
29 | private LocalDate borrowDate;
30 |
31 | public LocalDate getDueDate() {
32 | return borrowDate.plusDays(28);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/BorrowingRepository.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 | import org.springframework.data.jpa.repository.JpaRepository;
6 | import org.springframework.data.jpa.repository.Modifying;
7 |
8 | public interface BorrowingRepository extends JpaRepository {
9 |
10 | Optional findByBorrowedBook(Book book);
11 |
12 | @Modifying
13 | void deleteByBorrowerEmailAddress(String borrowerEmailAddress);
14 |
15 | List findByBorrowerEmailAddressOrderByBorrowDateAsc(String borrowerEmailAddress);
16 | }
17 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/java/de/codecentric/psd/worblehat/domain/StandardBookService.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import lombok.NonNull;
4 | import lombok.RequiredArgsConstructor;
5 | import org.springframework.stereotype.Service;
6 | import org.springframework.transaction.annotation.Transactional;
7 |
8 | import java.util.List;
9 | import java.util.Optional;
10 | import java.util.Set;
11 |
12 | /**
13 | * The domain service class for book operations.
14 | */
15 | @Service
16 | @Transactional
17 | @RequiredArgsConstructor
18 | public class StandardBookService implements BookService {
19 |
20 | @NonNull
21 | private final BorrowingRepository borrowingRepository;
22 |
23 | @NonNull
24 | private final BookRepository bookRepository;
25 |
26 | @Override
27 | public void returnAllBooksByBorrower(String borrowerEmailAddress) {
28 | borrowingRepository.deleteByBorrowerEmailAddress(borrowerEmailAddress);
29 | }
30 |
31 | @Override
32 | public Optional borrowBook(String isbn, String borrower) {
33 | Set books = bookRepository.findByIsbn(isbn);
34 |
35 | Optional unborrowedBook =
36 | books.stream().filter(book -> book.getBorrowing() == null).findFirst();
37 |
38 | return unborrowedBook.map(
39 | book -> {
40 | book.borrowNowByBorrower(borrower);
41 | borrowingRepository.save(book.getBorrowing());
42 | return book.getBorrowing();
43 | });
44 | }
45 |
46 | @Override
47 | public Set findBooksByIsbn(String isbn) {
48 | return bookRepository.findByIsbn(isbn); // null if not found
49 | }
50 |
51 | @Override
52 | public List findAllBooks() {
53 | return bookRepository.findAllByOrderByTitle();
54 | }
55 |
56 | @Override
57 | public Optional createBook(BookParameter bookParameter) {
58 | Book book = new Book(bookParameter);
59 |
60 | Optional bookFromRepo = bookRepository.findTopByIsbn(bookParameter.getIsbn());
61 |
62 | if (!bookFromRepo.isPresent() || book.isSameCopy(bookFromRepo.get())) {
63 | return Optional.of(bookRepository.save(book));
64 | } else return Optional.empty();
65 | }
66 |
67 | @Override
68 | public boolean bookExists(String isbn) {
69 | Set books = bookRepository.findByIsbn(isbn);
70 | return !books.isEmpty();
71 | }
72 |
73 | @Override
74 | public void deleteAllBooks() {
75 | borrowingRepository.deleteAll();
76 | bookRepository.deleteAll();
77 | }
78 |
79 | @Override
80 | public List findAllBorrowingsByEmailAddress(String emailAddress) {
81 | return borrowingRepository.findByBorrowerEmailAddressOrderByBorrowDateAsc(emailAddress);
82 | }
83 |
84 | @Override
85 | public void removeBook(String isbn) {
86 | Set books = bookRepository.findByIsbn(isbn);
87 |
88 | Optional bookToDelete = books.stream()
89 | .filter(book -> book.getBorrowing() == null)
90 | .findFirst();
91 |
92 | bookToDelete.ifPresent(bookRepository::delete);
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/resources/de/codecentric/psd/worblehat/liquibase-changesets/version-1.0/001-initialize-db.sql:
--------------------------------------------------------------------------------
1 | -- liquibase formatted sql
2 |
3 | -- changeset action:create_table_books
4 | CREATE TABLE book (
5 | id BIGSERIAL PRIMARY KEY,
6 | title VARCHAR(255),
7 | author VARCHAR(255),
8 | edition VARCHAR(255),
9 | isbn VARCHAR(255),
10 | year_of_publication INT,
11 | UNIQUE (isbn)
12 | );
13 |
14 | -- changeset action:create_table_borrowing
15 | CREATE TABLE borrowing(
16 | id BIGSERIAL PRIMARY KEY,
17 | borrow_date DATE,
18 | borrower_email_address VARCHAR(255),
19 | borrowed_book_id BIGINT,
20 | FOREIGN KEY (borrowed_book_id)
21 | REFERENCES book(id)
22 | ON DELETE CASCADE
23 | );
24 |
25 | -- changeset action:insert_demo_data
26 | INSERT INTO book(title, author, edition, isbn, year_of_publication)
27 | VALUES
28 | ('Harry Potter and the Philisopher''s Stone', 'J.K. Rowling', '', '0747532699', 1997),
29 | ('Harry Potter and the Prisoner of Azkaban', 'J.K. Rowling', '', '0747542155', 1999),
30 | ('Harry Potter and the Goblet of Fire', 'J.K. Rowling', '', '074754624X', 2000),
31 | ('Harry Potter and the Order of the Phoenix', 'J.K. Rowling', '', '0747551006', 2003),
32 | ('Harry Potter and the Half-Blood Prince', 'J.K. Rowling', '', '0747581088', 2005),
33 | ('Harry Potter and the Deathly Hallows', 'J.K. Rowling', '', '0545010225', 2007);
34 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/resources/de/codecentric/psd/worblehat/liquibase-changesets/version-1.0/002-remove-unique-isbn-constraint.sql:
--------------------------------------------------------------------------------
1 | -- liquibase formatted sql
2 |
3 | -- changeset action:drop_index
4 | ALTER TABLE book DROP CONSTRAINT book_isbn_key;
5 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/resources/de/codecentric/psd/worblehat/liquibase-changesets/version-1.2/001-add-description.sql:
--------------------------------------------------------------------------------
1 | -- liquibase formatted sql
2 |
3 | -- changeset action:add_description
4 | ALTER TABLE book ADD COLUMN description TEXT;
5 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/resources/de/codecentric/psd/worblehat/liquibase-changesets/version-1.2/002-halt-on-invalid-book-data-for-isbn-editions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 | select count(*) from (select count(distinct book.edition) as edition_count from
12 | book
13 | group by isbn) as edition_counts where edition_count > 1
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/worblehat-domain/src/main/resources/master.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
9 |
10 |
12 |
13 |
--------------------------------------------------------------------------------
/worblehat-domain/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
26 |
27 |
28 | org.apache.maven.skins
29 | maven-fluido-skin
30 | 1.8
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/worblehat-domain/src/test/java/de/codecentric/psd/worblehat/domain/BookTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import static org.hamcrest.CoreMatchers.*;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 |
9 | class BookTest {
10 |
11 | private Book book;
12 |
13 | @BeforeEach
14 | void setup() {
15 | book = new Book("Titel", "Author", "2", "1", 1234);
16 | }
17 |
18 | @Test
19 | void shouldCreateCompleteObjectFromParameter() {
20 | Book b = new Book(new BookParameter("Title", "Author", "2", "1", 1234, "Description"));
21 | assertThat(b.getTitle(), is("Title"));
22 | assertThat(b.getAuthor(), is("Author"));
23 | assertThat(b.getEdition(), is("2"));
24 | assertThat(b.getIsbn(), is("1"));
25 | assertThat(b.getYearOfPublication(), is(1234));
26 | assertThat(b.getDescription(), is("Description"));
27 | }
28 |
29 | @Test
30 | void shouldNotShallowCopy() {
31 | book.borrowNowByBorrower("andreas");
32 | Book anotherCopy = new Book(book);
33 | assertThat(book.getBorrowing(), not(sameInstance(anotherCopy.getBorrowing())));
34 | }
35 |
36 | @Test
37 | void shouldReturnFalseWhenAuthorisDifferent() {
38 | Book anotherCopy = new Book(book);
39 | anotherCopy.setAuthor("Bene");
40 | assertThat(book.isSameCopy(anotherCopy), is(false));
41 | }
42 |
43 | @Test
44 | void shouldReturnFalseWhenTitleisDifferent() {
45 | Book anotherCopy = new Book(book);
46 | anotherCopy.setTitle("Lord of the Rings");
47 | assertThat(book.isSameCopy(anotherCopy), is(false));
48 | }
49 |
50 | @Test
51 | void shouldReturnFalseWhenEditionIsDifferent() {
52 | Book anotherCopy = new Book(book);
53 | anotherCopy.setEdition("3");
54 | assertThat(book.isSameCopy(anotherCopy), is(false));
55 | }
56 |
57 | @Test
58 | void shouldReturnTrueWhenAllButTitleAndAuthorAndEditionAreDifferent() {
59 | Book anotherCopy = new Book(book);
60 | anotherCopy.setIsbn("123456789X");
61 | anotherCopy.setYearOfPublication(2010);
62 | assertThat(book.isSameCopy(anotherCopy), is(true));
63 | }
64 |
65 | @Test
66 | void shouldBeBorrowable() {
67 | book.borrowNowByBorrower("a@bc.de");
68 | assertThat(book.getBorrowing().getBorrowerEmailAddress(), is("a@bc.de"));
69 | }
70 |
71 | @Test
72 | void shouldIgnoreNewBorrowWhenBorrowed() {
73 | book.borrowNowByBorrower("a@bc.de");
74 | book.borrowNowByBorrower("a@bc.ru");
75 | assertThat(book.getBorrowing().getBorrowerEmailAddress(), is("a@bc.de"));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/worblehat-domain/src/test/java/de/codecentric/psd/worblehat/domain/BorrowingTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.domain;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import java.time.LocalDate;
6 | import java.time.Month;
7 |
8 | import static org.hamcrest.CoreMatchers.is;
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 |
11 | class BorrowingTest {
12 |
13 | @Test
14 | void getDueDate() {
15 | var borrowingUnderTest = new Borrowing();
16 | var now = LocalDate.of(2020, Month.OCTOBER, 1);
17 | borrowingUnderTest.setBorrowDate(now);
18 |
19 | LocalDate dueDate = borrowingUnderTest.getDueDate();
20 | assertThat(dueDate, is(LocalDate.of(2020, Month.OCTOBER, 29)));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/worblehat-web/.checkstyle:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/worblehat-web/.eclipse-pmd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/worblehat-web/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | java
3 | id("org.springframework.boot") version "2.6.6"
4 | }
5 |
6 | apply(plugin = "io.spring.dependency-management")
7 |
8 | repositories {
9 | mavenCentral()
10 | }
11 |
12 | dependencies {
13 | implementation(project(":worblehat-domain"))
14 |
15 | implementation("org.springframework.boot:spring-boot-starter-web")
16 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
17 | implementation("org.springframework:spring-tx")
18 | implementation("javax.persistence:javax.persistence-api")
19 |
20 | runtimeOnly("org.springframework.boot:spring-boot-devtools")
21 |
22 | implementation("org.apache.commons:commons-lang3")
23 | implementation("commons-validator:commons-validator:1.9.0")
24 | compileOnly("org.projectlombok:lombok:1.18.32")
25 | annotationProcessor("org.projectlombok:lombok:1.18.32")
26 |
27 | testImplementation("org.springframework.boot:spring-boot-starter-test")
28 | testImplementation("org.mockito:mockito-junit-jupiter")
29 | testImplementation("org.junit.jupiter:junit-jupiter-api")
30 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
31 | testImplementation("org.junit.jupiter:junit-jupiter-params")
32 | testImplementation(platform("org.testcontainers:testcontainers-bom:1.19.8"))
33 | testImplementation("org.testcontainers:postgresql")
34 | testImplementation("com.google.guava:guava:30.1.1-jre") // TODO, replace with Set.of
35 | }
36 |
37 | tasks {
38 | jar {
39 | enabled = true
40 | }
41 | bootJar {
42 | classifier = "executable"
43 | }
44 | test {
45 | useJUnitPlatform()
46 | }
47 | processResources {
48 | filesMatching("*.properties") {
49 | expand(project.properties)
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/worblehat-web/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 |
6 | de.codecentric.psd
7 | worblehat
8 | 1.4.0-SNAPSHOT
9 | ../
10 |
11 |
12 | Worblehat Bookmanager
13 | worblehat-web
14 | jar
15 |
16 |
17 | de.codecentric.psd.Worblehat
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter-web
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-starter-validation
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-thymeleaf
32 |
33 |
34 | org.postgresql
35 | postgresql
36 |
37 |
38 | de.codecentric.psd
39 | worblehat-domain
40 | ${project.version}
41 |
42 |
43 |
44 | org.projectlombok
45 | lombok
46 | provided
47 |
48 |
49 |
50 | org.apache.commons
51 | commons-lang3
52 |
53 |
54 |
55 | commons-validator
56 | commons-validator
57 |
58 |
59 |
60 | org.springframework.boot
61 | spring-boot-devtools
62 |
63 |
64 | org.springframework.boot
65 | spring-boot-starter-actuator
66 |
67 |
68 | de.codecentric
69 | chaos-monkey-spring-boot
70 | 2.7.2
71 |
72 |
73 |
74 | org.springframework.boot
75 | spring-boot-starter-test
76 | test
77 |
78 |
79 | org.junit.jupiter
80 | junit-jupiter-engine
81 | test
82 |
83 |
84 | org.mockito
85 | mockito-core
86 | test
87 |
88 |
89 | org.mockito
90 | mockito-junit-jupiter
91 | test
92 |
93 |
94 | org.junit.jupiter
95 | junit-jupiter-params
96 | test
97 |
98 |
99 | org.testcontainers
100 | postgresql
101 | test
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | org.springframework.boot
110 | spring-boot-maven-plugin
111 |
112 | executable
113 | true
114 |
115 |
116 |
117 | org.apache.maven.plugins
118 | maven-failsafe-plugin
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/Worblehat.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class Worblehat {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(Worblehat.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/BookDetailsController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.BookService;
5 | import java.util.Set;
6 | import lombok.NonNull;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.stereotype.Controller;
9 | import org.springframework.ui.ModelMap;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 | import org.springframework.web.bind.annotation.RequestParam;
13 |
14 | @Controller
15 | @RequestMapping("/bookDetails")
16 | @RequiredArgsConstructor
17 | public class BookDetailsController {
18 | private static final String BOOK_DETAILS = "bookDetails";
19 |
20 | private static final String REDIRECT_TO_BOOK_LIST = "redirect:bookList";
21 |
22 | @NonNull private final BookService bookService;
23 |
24 | @GetMapping
25 | public String setupForm(ModelMap modelMap, @RequestParam String isbn) {
26 | Set books = bookService.findBooksByIsbn(isbn);
27 | if (books.isEmpty()) {
28 | return REDIRECT_TO_BOOK_LIST;
29 | }
30 | Book book = books.iterator().next();
31 | modelMap.addAttribute("book", book);
32 | return BOOK_DETAILS;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/BookListController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.BookService;
5 | import java.util.List;
6 | import java.util.Map;
7 | import java.util.stream.Collectors;
8 | import lombok.NonNull;
9 | import lombok.RequiredArgsConstructor;
10 | import org.springframework.stereotype.Controller;
11 | import org.springframework.ui.ModelMap;
12 | import org.springframework.web.bind.annotation.GetMapping;
13 | import org.springframework.web.bind.annotation.RequestMapping;
14 |
15 | /** Controller class for the book table result. */
16 | @Controller
17 | @RequestMapping("/bookList")
18 | @RequiredArgsConstructor
19 | public class BookListController {
20 |
21 | @NonNull private final BookService bookService;
22 |
23 | @GetMapping
24 | public String setupForm(ModelMap modelMap) {
25 | List books = bookService.findAllBooks();
26 | modelMap.addAttribute("books", books);
27 | Map covers =
28 | getCoverURLsForBooks(books.stream().map(Book::getIsbn).collect(Collectors.toList()));
29 | modelMap.addAttribute("covers", covers);
30 | return "bookList";
31 | }
32 |
33 | protected Map getCoverURLsForBooks(List isbns) {
34 | return isbns.stream()
35 | .collect(
36 | Collectors.toMap(
37 | isbn -> isbn,
38 | isbn ->
39 | "http://covers.openlibrary.org/b/isbn/"
40 | + isbn.trim().replace("-", "").replace(" ", "")
41 | + "-S.jpg",
42 | (x1, x2) -> x1));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/BorrowBookController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.BookService;
5 | import de.codecentric.psd.worblehat.domain.Borrowing;
6 | import de.codecentric.psd.worblehat.web.formdata.BorrowBookFormData;
7 | import java.util.Optional;
8 | import java.util.Set;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.validation.Valid;
11 | import lombok.NonNull;
12 | import lombok.RequiredArgsConstructor;
13 | import org.springframework.stereotype.Controller;
14 | import org.springframework.transaction.annotation.Transactional;
15 | import org.springframework.ui.ModelMap;
16 | import org.springframework.validation.BindingResult;
17 | import org.springframework.web.bind.annotation.ExceptionHandler;
18 | import org.springframework.web.bind.annotation.GetMapping;
19 | import org.springframework.web.bind.annotation.ModelAttribute;
20 | import org.springframework.web.bind.annotation.PostMapping;
21 | import org.springframework.web.bind.annotation.RequestMapping;
22 |
23 | /** Controller for BorrowingBook */
24 | @RequestMapping("/borrow")
25 | @Controller
26 | @RequiredArgsConstructor
27 | public class BorrowBookController {
28 |
29 | private static final String BORROW_PAGE = "borrow";
30 |
31 | @NonNull private final BookService bookService;
32 |
33 | @GetMapping
34 | public void setupForm(ModelMap model) {
35 | model.put("borrowFormData", new BorrowBookFormData());
36 | }
37 |
38 | @Transactional
39 | @PostMapping
40 | public String processSubmit(
41 | @ModelAttribute("borrowFormData") @Valid BorrowBookFormData borrowFormData,
42 | BindingResult result) {
43 | if (result.hasErrors()) {
44 | return BORROW_PAGE;
45 | }
46 | Set books = bookService.findBooksByIsbn(borrowFormData.getIsbn());
47 | if (books.isEmpty()) {
48 | result.rejectValue("isbn", "noBookExists");
49 | return BORROW_PAGE;
50 | }
51 | Optional borrowing =
52 | bookService.borrowBook(borrowFormData.getIsbn(), borrowFormData.getEmail());
53 |
54 | return borrowing
55 | .map(b -> "home")
56 | .orElseGet(
57 | () -> {
58 | result.rejectValue("isbn", "noBorrowableBooks");
59 | return BORROW_PAGE;
60 | });
61 | }
62 |
63 | @ExceptionHandler(Exception.class)
64 | public String handleErrors(Exception ex, HttpServletRequest request) {
65 | return "home";
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/BorrowedBookListController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.BookService;
4 | import de.codecentric.psd.worblehat.domain.Borrowing;
5 | import de.codecentric.psd.worblehat.web.formdata.ReturnAllBooksFormData;
6 | import java.util.List;
7 | import javax.validation.Valid;
8 | import lombok.NonNull;
9 | import lombok.RequiredArgsConstructor;
10 | import org.springframework.stereotype.Controller;
11 | import org.springframework.ui.ModelMap;
12 | import org.springframework.validation.BindingResult;
13 | import org.springframework.web.bind.annotation.GetMapping;
14 | import org.springframework.web.bind.annotation.ModelAttribute;
15 | import org.springframework.web.bind.annotation.PostMapping;
16 | import org.springframework.web.bind.annotation.RequestMapping;
17 |
18 | @RequestMapping("/borrowedBookList")
19 | @Controller
20 | @RequiredArgsConstructor
21 | public class BorrowedBookListController {
22 |
23 | // TODO RÜckgabe wert nicht vergessen !!!!!!
24 |
25 | @NonNull private final BookService bookService;
26 |
27 | @GetMapping
28 | public void get(ModelMap modelMap) {
29 | modelMap.put("returnAllBookFormData", new ReturnAllBooksFormData());
30 | }
31 |
32 | @PostMapping
33 | public String findBorrowedBooksByEmailAddress(
34 | @ModelAttribute("returnAllBookFormData") @Valid ReturnAllBooksFormData returnAllBooksFormData,
35 | BindingResult result,
36 | ModelMap modelMap) {
37 | List borrowings =
38 | bookService.findAllBorrowingsByEmailAddress(returnAllBooksFormData.getEmailAddress());
39 |
40 | modelMap.put("borrowings", borrowings);
41 |
42 | return "borrowedBookList";
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/ControllerSetup.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import org.springframework.beans.propertyeditors.StringTrimmerEditor;
4 | import org.springframework.web.bind.WebDataBinder;
5 | import org.springframework.web.bind.annotation.ControllerAdvice;
6 | import org.springframework.web.bind.annotation.InitBinder;
7 |
8 | @ControllerAdvice
9 | public class ControllerSetup {
10 | @InitBinder
11 | public void customizeBinding(WebDataBinder binder) {
12 | binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/InsertBookController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.BookService;
5 | import de.codecentric.psd.worblehat.web.formdata.InsertBookFormData;
6 | import java.util.Optional;
7 | import javax.validation.Valid;
8 | import lombok.NonNull;
9 | import lombok.RequiredArgsConstructor;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.stereotype.Controller;
13 | import org.springframework.ui.ModelMap;
14 | import org.springframework.validation.BindingResult;
15 | import org.springframework.web.bind.annotation.GetMapping;
16 | import org.springframework.web.bind.annotation.ModelAttribute;
17 | import org.springframework.web.bind.annotation.PostMapping;
18 | import org.springframework.web.bind.annotation.RequestMapping;
19 |
20 | /** Handles requests for the application home page. */
21 | @Controller
22 | @RequestMapping("/insertBooks")
23 | @RequiredArgsConstructor
24 | public class InsertBookController {
25 |
26 | private static final Logger LOG = LoggerFactory.getLogger(InsertBookController.class);
27 |
28 | @NonNull private final BookService bookService;
29 |
30 | @GetMapping
31 | public void setupForm(ModelMap modelMap) {
32 | modelMap.put("insertBookFormData", new InsertBookFormData());
33 | }
34 |
35 | @PostMapping
36 | public String processSubmit(
37 | @ModelAttribute("insertBookFormData") @Valid InsertBookFormData insertBookFormData,
38 | BindingResult result) {
39 |
40 | if (result.hasErrors()) {
41 | return "insertBooks";
42 | } else {
43 | Optional book = bookService.createBook(insertBookFormData.toBookParameter());
44 | if (book.isPresent()) {
45 | LOG.info("new book instance is created: " + book.get());
46 | } else {
47 | LOG.debug("failed to create new book with: " + insertBookFormData.toString());
48 | }
49 | return "redirect:bookList";
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/NavigationController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import org.springframework.stereotype.Controller;
4 | import org.springframework.web.bind.annotation.GetMapping;
5 |
6 | /** Controller for Navigation */
7 | @Controller
8 | public class NavigationController {
9 |
10 | @GetMapping(value = "/")
11 | public String home() {
12 | return "home";
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/RemoveBookController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.BookService;
4 | import lombok.NonNull;
5 | import lombok.RequiredArgsConstructor;
6 | import org.springframework.stereotype.Controller;
7 | import org.springframework.web.bind.annotation.GetMapping;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestParam;
10 |
11 | @Controller
12 |
13 | @RequestMapping("/removeBook")
14 | @RequiredArgsConstructor
15 | public class RemoveBookController {
16 | private static final String REDIRECT_TO_BOOK_LIST = "redirect:bookList";
17 |
18 | @NonNull private final BookService bookService;
19 |
20 | @GetMapping
21 | public String removeBook(@RequestParam String isbn) {
22 | bookService.removeBook(isbn);
23 | return REDIRECT_TO_BOOK_LIST;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/controller/ReturnAllBooksController.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.BookService;
4 | import de.codecentric.psd.worblehat.web.formdata.ReturnAllBooksFormData;
5 | import javax.validation.Valid;
6 | import lombok.NonNull;
7 | import lombok.RequiredArgsConstructor;
8 | import org.springframework.stereotype.Controller;
9 | import org.springframework.ui.ModelMap;
10 | import org.springframework.validation.BindingResult;
11 | import org.springframework.web.bind.annotation.GetMapping;
12 | import org.springframework.web.bind.annotation.ModelAttribute;
13 | import org.springframework.web.bind.annotation.PostMapping;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 |
16 | /** Controller class for the */
17 | @Controller
18 | @RequestMapping("/returnAllBooks")
19 | @RequiredArgsConstructor
20 | public class ReturnAllBooksController {
21 |
22 | @NonNull private final BookService bookService;
23 |
24 | @GetMapping
25 | public void prepareView(ModelMap modelMap) {
26 | modelMap.put("returnAllBookFormData", new ReturnAllBooksFormData());
27 | }
28 |
29 | @PostMapping
30 | public String returnAllBooks(
31 | @ModelAttribute("returnAllBookFormData") @Valid ReturnAllBooksFormData formData,
32 | BindingResult result) {
33 | if (result.hasErrors()) {
34 | return "returnAllBooks";
35 | } else {
36 | bookService.returnAllBooksByBorrower(formData.getEmailAddress());
37 | return "home";
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/formdata/BorrowBookFormData.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.formdata;
2 |
3 | import de.codecentric.psd.worblehat.web.validation.ISBN;
4 | import javax.validation.constraints.Email;
5 | import javax.validation.constraints.NotEmpty;
6 | import lombok.Data;
7 |
8 | /** Form data object from the borrow view. */
9 | @Data
10 | public class BorrowBookFormData {
11 |
12 | @NotEmpty(message = "{empty.isbn}")
13 | @ISBN(message = "{invalid.isbn}")
14 | private String isbn;
15 |
16 | @NotEmpty(message = "{empty.email}")
17 | @Email(message = "{invalid.email}")
18 | private String email;
19 | }
20 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/formdata/InsertBookFormData.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.formdata;
2 |
3 | import de.codecentric.psd.worblehat.domain.BookParameter;
4 | import de.codecentric.psd.worblehat.web.validation.ISBN;
5 | import de.codecentric.psd.worblehat.web.validation.Numeric;
6 | import javax.validation.constraints.NotEmpty;
7 | import lombok.Data;
8 | import org.hibernate.validator.constraints.Length;
9 |
10 | /** This class represent the form data of the add book form. */
11 | @Data
12 | public class InsertBookFormData {
13 |
14 | @NotEmpty(message = "{empty.title}")
15 | private String title;
16 |
17 | @NotEmpty(message = "{empty.edition}")
18 | @Numeric(message = "{invalid.edition}")
19 | private String edition;
20 |
21 | @NotEmpty(message = "{empty.yearOfPublication}")
22 | @Numeric(message = "{invalid.yearOfPublication}")
23 | @Length(message = "{invalid.length.yearOfPublication}", min = 4, max = 4)
24 | private String yearOfPublication;
25 |
26 | @NotEmpty(message = "{empty.isbn}")
27 | @ISBN(message = "{invalid.isbn}")
28 | private String isbn;
29 |
30 | @NotEmpty(message = "{empty.author}")
31 | private String author;
32 |
33 | private String description;
34 |
35 | public BookParameter toBookParameter() {
36 | return new BookParameter(
37 | title, author, edition, isbn, new Integer(yearOfPublication), description);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/formdata/ReturnAllBooksFormData.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.formdata;
2 |
3 | import javax.validation.constraints.Email;
4 | import javax.validation.constraints.NotEmpty;
5 | import lombok.Data;
6 |
7 | /** This class represent the form data of the return book form. */
8 | @Data
9 | public class ReturnAllBooksFormData {
10 |
11 | @NotEmpty(message = "{empty.email}")
12 | @Email(message = "{invalid.email}")
13 | private String emailAddress;
14 | }
15 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/validation/ISBN.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import static java.lang.annotation.ElementType.*;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 | import javax.validation.Constraint;
10 | import javax.validation.Payload;
11 |
12 | @Target({METHOD, FIELD, ANNOTATION_TYPE})
13 | @Retention(RUNTIME)
14 | @Constraint(validatedBy = ISBNConstraintValidator.class)
15 | @Documented
16 | public @interface ISBN {
17 |
18 | String message() default "{de.codecentric.psd.worblehat.web.validation.ISBN}";
19 |
20 | Class>[] groups() default {};
21 |
22 | Class extends Payload>[] payload() default {};
23 | }
24 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/validation/ISBNConstraintValidator.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import javax.validation.ConstraintValidator;
4 | import javax.validation.ConstraintValidatorContext;
5 | import org.apache.commons.lang3.StringUtils;
6 | import org.apache.commons.validator.routines.ISBNValidator;
7 |
8 | public class ISBNConstraintValidator implements ConstraintValidator {
9 |
10 | private static final ISBNValidator INSTANCE = ISBNValidator.getInstance();
11 |
12 | @Override
13 | public boolean isValid(String isbn, ConstraintValidatorContext context) {
14 | // Don't validate null, empty and blank strings, since these are validated by @NotNull,
15 | // @NotEmpty and @NotBlank
16 | if (StringUtils.isNotBlank(isbn)) {
17 | return INSTANCE.isValid(isbn);
18 | }
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/validation/Numeric.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import static java.lang.annotation.ElementType.*;
4 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
5 |
6 | import java.lang.annotation.Documented;
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.Target;
9 | import javax.validation.Constraint;
10 | import javax.validation.Payload;
11 |
12 | @Target({METHOD, FIELD, ANNOTATION_TYPE})
13 | @Retention(RUNTIME)
14 | @Constraint(validatedBy = NumericConstraintValidator.class)
15 | @Documented
16 | public @interface Numeric {
17 |
18 | String message() default "{de.codecentric.psd.worblehat.web.validation.Numeric}";
19 |
20 | Class>[] groups() default {};
21 |
22 | Class extends Payload>[] payload() default {};
23 | }
24 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/java/de/codecentric/psd/worblehat/web/validation/NumericConstraintValidator.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import javax.validation.ConstraintValidator;
4 | import javax.validation.ConstraintValidatorContext;
5 | import org.apache.commons.lang3.StringUtils;
6 |
7 | public class NumericConstraintValidator implements ConstraintValidator {
8 |
9 | @Override
10 | public void initialize(Numeric constraintAnnotation) {}
11 |
12 | @Override
13 | public boolean isValid(String value, ConstraintValidatorContext context) {
14 | // Don't validate null, empty and blank strings, since these are validated by @NotNull,
15 | // @NotEmpty and @NotBlank
16 | if (StringUtils.isNotBlank(value)) {
17 | return StringUtils.isNumeric(value);
18 | }
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/ValidationMessages.properties:
--------------------------------------------------------------------------------
1 | # email
2 | invalid.email=Please enter an email address like bob@example.com
3 | empty.email=Please enter Your email address
4 | # ISBN
5 | empty.isbn=Please enter the book''s ISBN
6 | invalid.isbn=Please enter a 10-digit ISBN
7 | # title
8 | empty.title=Please enter the book''s title
9 | # edition
10 | empty.edition=Please enter the edition
11 | invalid.edition=Please enter a numeric value for the edition
12 | # year of publication
13 | empty.yearOfPublication=Please enter the year of publication
14 | invalid.yearOfPublication=Please enter only digits for a year
15 | invalid.length.yearOfPublication=Please enter a 4-digit year
16 | # author
17 | empty.author=Please enter the author
18 | # Default messages for custom validators
19 | de.codecentric.psd.worblehat.web.validation.ISBN=Please enter a 10-digit ISBN
20 | de.codecentric.psd.worblehat.web.validation.Numeric=Please enter a numeric value
21 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # Database Config
2 | spring.datasource.url=jdbc:postgresql:postgres
3 | spring.datasource.username=postgres
4 | spring.datasource.password=worblehat-pw
5 | # Spring Boot Admin Config
6 | #spring.boot.admin.url=http://localhost:8888
7 | # Hint: Read about resource filtering with Spring Boot and Maven:
8 | # https://docs.spring.io/spring-boot/docs/current/reference/html/howto-properties-and-configuration.html
9 |
10 | #gradle
11 | #spring.application.name=${applicationName}
12 | #info.version=${version}
13 | #info.stage=test
14 | #logging.file=/tmp/${name}.log
15 |
16 | #maven
17 | spring.application.name=@pom.name@
18 | info.version=@pom.version@
19 | info.stage=test
20 | logging.file=/tmp/@pom.artifactId@.log
21 |
22 |
23 |
24 | spring.liquibase.change-log=classpath:master.xml
25 | # set this to true from command line in order to start with a fresh DB on every start
26 | spring.liquibase.drop-first=false
27 | spring.jpa.hibernate.ddl-auto=validate
28 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
29 | spring.thymeleaf.cache=false
30 |
31 | spring.profiles.active=chaos-monkey
32 | chaos.monkey.enabled=false
33 |
34 | chaos.monkey.watcher.controller=true
35 | chaos.monkey.watcher.restController=true
36 | chaos.monkey.watcher.service=true
37 | chaos.monkey.watcher.repository=true
38 |
39 | chaos.monkey.assaults.level=1
40 | chaos.monkey.assaults.latencyRangeStart=1
41 | chaos.monkey.assaults.latencyRangeEnd=1
42 | chaos.monkey.assaults.exceptionsActive=false
43 | chaos.monkey.assaults.killApplicationActive=false
44 |
45 | management.endpoint.chaosmonkey.enabled=true
46 | management.endpoint.chaosmonkeyjmx.enabled=true
47 |
48 | # include specific endpoints
49 | management.endpoints.web.exposure.include=health,info,chaosmonkey
50 | management.security.enabled=false
51 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .o8 oooo oooo .
5 | "888 `888 `888 .o8
6 | oooo oooo ooo .ooooo. oooo d8b 888oooo. 888 .ooooo. 888 .oo. .oooo. .o888oo
7 | `88. `88. .8' d88' `88b `888""8P d88' `88b 888 d88' `88b 888P"Y88b `P )88b 888
8 | `88..]88..8' 888 888 888 888 888 888 888ooo888 888 888 .oP"888 888
9 | `888'`888' 888 888 888 888 888 888 888 .o 888 888 d8( 888 888 .
10 | `8' `8' `Y8bod8P' d888b `Y8bod8P' o888o `Y8bod8P' o888o o888o `Y888""8o "888"
11 | ${spring.application.name} – Version ${info.version}
12 | © codecentric AG 2010-2020
13 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/messages.properties:
--------------------------------------------------------------------------------
1 | noBookExists=There is no book with this ISBN.
2 | noBorrowableBooks=The book is already borrowed.
3 | duplicateIsbn=A book with this ISBN already exists.
4 | internalError=An internal error occurred.
5 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/bookDetails.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Worblehat Bookmanager
5 |
6 |
7 |
8 |
9 |
10 |
11 | ISBN: |
12 | ISBN |
13 |
14 |
15 | Author: |
16 | Author |
17 |
18 |
19 | Year: |
20 | Year |
21 |
22 |
23 | Edition: |
24 | Year |
25 |
26 |
27 | Description: |
28 |
29 |
30 | Description |
31 |
32 |
33 | Remove Book
34 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/bookList.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Show all Books
6 |
7 |
8 |
9 | All The Books
10 |
11 |
12 |
13 |
14 | Cover |
15 | Title |
16 | Author |
17 | Year |
18 | Edition |
19 | ISBN |
20 | Description |
21 | Borrower |
22 | Details |
23 |
24 |
25 |
26 |
27 | ![]() |
28 | Harry Potter |
29 | J.K. Rowling |
30 | 2002 |
31 | 1st |
32 | 123456 |
33 | Description |
34 | someone@codecentric.de |
35 | Details
36 | |
37 |
38 |
39 |
40 |
41 | Footer
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/borrow.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Borrow book - Worblehat Bookmanager
5 |
6 |
7 | Borrow Book
8 |
9 |
18 |
19 | Footer
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/borrowedBookList.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Show All Borrowed Books
6 |
7 |
8 |
9 |
10 | Search For Borrowed Books By
11 |
12 |
19 |
20 | All My Borrowed Books
21 |
22 |
23 |
24 |
25 | Title |
26 | Author |
27 | ISBN |
28 | Due Date |
29 |
30 |
31 |
32 |
33 | |
34 | |
35 | |
36 | |
37 |
38 |
39 |
40 |
41 |
42 | Footer
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/fragments/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Footer Fragment
5 |
6 |
7 |
8 |
13 |
14 | Version: 1.0
15 |
16 |
17 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Worblehat Bookmanager
5 |
6 |
7 |
8 |
9 |
16 |
17 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/insertBooks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Add Book - Worblehat Bookmanager
5 |
6 |
7 | Add Book
8 |
9 |
32 |
33 | Footer
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/worblehat-web/src/main/resources/templates/returnAllBooks.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Return All Books - Worblehat Bookmanager
5 |
6 |
7 | Return all Books
8 |
9 |
16 |
17 | Footer
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/worblehat-web/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
21 |
22 |
26 |
27 |
28 | org.apache.maven.skins
29 | maven-fluido-skin
30 | 1.8
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/WorblehatDev.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.testcontainers.containers.PostgreSQLContainer;
5 |
6 | public class WorblehatDev {
7 |
8 | public static final String POSTGRES_STARTED_PROP = "postgresStarted";
9 |
10 | public static void main(String[] args) {
11 |
12 | String postgresStarted = System.getProperty(POSTGRES_STARTED_PROP);
13 | if (postgresStarted == null) {
14 | initPostgresContainer();
15 | System.setProperty("postgresStarted", "true");
16 | }
17 |
18 | SpringApplication.run(Worblehat.class, args);
19 | }
20 |
21 | private static void initPostgresContainer() {
22 | PostgreSQLContainer postgreSQLContainer =
23 | new PostgreSQLContainer<>().withUsername("foo").withPassword("bar");
24 |
25 | postgreSQLContainer.start();
26 |
27 | System.setProperty("spring.datasource.url", postgreSQLContainer.getJdbcUrl());
28 | System.setProperty("spring.datasource.username", postgreSQLContainer.getUsername());
29 | System.setProperty("spring.datasource.password", postgreSQLContainer.getPassword());
30 | System.setProperty("spring.datasource.driver-class-name", "");
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/WorblehatSystemIT.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.junit.jupiter.api.extension.ExtendWith;
5 | import org.springframework.boot.test.context.SpringBootTest;
6 | import org.springframework.test.context.junit.jupiter.SpringExtension;
7 |
8 | @SpringBootTest()
9 | @ExtendWith(SpringExtension.class)
10 | class WorblehatSystemIT {
11 |
12 | @Test
13 | void shouldStartApplication() {
14 | // Intentionally left blank, this test should just make sure that the application can be started
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/BookDetailsControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static org.hamcrest.CoreMatchers.containsString;
4 | import static org.hamcrest.CoreMatchers.is;
5 | import static org.hamcrest.MatcherAssert.assertThat;
6 | import static org.mockito.Mockito.when;
7 |
8 | import com.google.common.collect.ImmutableSet;
9 | import de.codecentric.psd.worblehat.domain.Book;
10 | import de.codecentric.psd.worblehat.domain.BookService;
11 | import org.junit.jupiter.api.BeforeEach;
12 | import org.junit.jupiter.api.Test;
13 | import org.mockito.InjectMocks;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.jupiter.MockitoSettings;
16 | import org.springframework.ui.ModelMap;
17 |
18 | @MockitoSettings
19 | public class BookDetailsControllerTest {
20 |
21 | @InjectMocks private BookDetailsController bookDetailsController;
22 |
23 | @Mock private BookService bookService;
24 |
25 | private ModelMap modelMap;
26 |
27 | public static final String ISBN = "123456789X";
28 | private static final Book TEST_BOOK = new Book("title", "author", "edition", "isbn", 2016);
29 |
30 | @BeforeEach
31 | void setUp() {
32 | modelMap = new ModelMap();
33 | when(bookService.findBooksByIsbn(ISBN)).thenReturn(ImmutableSet.of(TEST_BOOK));
34 | }
35 |
36 | @Test
37 | void shouldNavigateToBookDetails() {
38 | String url = bookDetailsController.setupForm(modelMap, ISBN);
39 | assertThat(url, containsString("bookDetails"));
40 | }
41 |
42 | @Test
43 | void shouldContainBookDetails() {
44 | bookDetailsController.setupForm(modelMap, ISBN);
45 | Book actualBook = (Book) modelMap.get("book");
46 | assertThat(actualBook, is(TEST_BOOK));
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/BookListControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 | import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
6 | import static org.mockito.Mockito.when;
7 |
8 | import de.codecentric.psd.worblehat.domain.Book;
9 | import de.codecentric.psd.worblehat.domain.BookService;
10 | import java.util.List;
11 | import java.util.Map;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.CsvSource;
16 | import org.mockito.InjectMocks;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.jupiter.MockitoSettings;
19 | import org.springframework.ui.ModelMap;
20 |
21 | @MockitoSettings
22 | class BookListControllerTest {
23 |
24 | @InjectMocks private BookListController bookListController;
25 |
26 | @Mock private BookService bookService;
27 |
28 | private static final Book TEST_BOOK = new Book("title", "author", "edition", "isbn", 2016);
29 | private static final Book TEST_BOOK2 = new Book("title2", "author2", "edition2", "isbn2", 2018);
30 |
31 | private ModelMap modelMap;
32 |
33 | @BeforeEach
34 | void setUp() {
35 | modelMap = new ModelMap();
36 | }
37 |
38 | @Test
39 | void shouldNavigateToBookList() {
40 | String url = bookListController.setupForm(modelMap);
41 | assertThat(url, is("bookList"));
42 | }
43 |
44 | @Test
45 | void shouldContainBooks() {
46 | List bookList = List.of(TEST_BOOK);
47 | when(bookService.findAllBooks()).thenReturn(bookList);
48 | bookListController.setupForm(modelMap);
49 | List actualBooks = (List) modelMap.get("books");
50 | assertThat(actualBooks, is(bookList));
51 | }
52 |
53 | @Test
54 | void shouldContainCovers() {
55 | List bookList = List.of(TEST_BOOK);
56 | when(bookService.findAllBooks()).thenReturn(bookList);
57 | bookListController.setupForm(modelMap);
58 | Map actualCovers = (Map) modelMap.get("covers");
59 | assertThat(actualCovers.values(), hasSize(1));
60 | }
61 |
62 | @ParameterizedTest
63 | @CsvSource({
64 | "isbn, http://covers.openlibrary.org/b/isbn/isbn-S.jpg",
65 | "isbn2, http://covers.openlibrary.org/b/isbn/isbn2-S.jpg",
66 | "123456789X, http://covers.openlibrary.org/b/isbn/123456789X-S.jpg",
67 | "99921-58-10-7, http://covers.openlibrary.org/b/isbn/9992158107-S.jpg",
68 | "960 425 059 0, http://covers.openlibrary.org/b/isbn/9604250590-S.jpg",
69 | "9780306406157, http://covers.openlibrary.org/b/isbn/9780306406157-S.jpg",
70 | "978-0-306-40615-7, http://covers.openlibrary.org/b/isbn/9780306406157-S.jpg",
71 | "978 0 306 40615 7, http://covers.openlibrary.org/b/isbn/9780306406157-S.jpg",
72 | })
73 | void shouldBuildCoverMapForBooks(String isbn, String URL) {
74 |
75 | Map coverURLsForBooks = bookListController.getCoverURLsForBooks(List.of(isbn));
76 | assertThat(coverURLsForBooks.get(isbn), is(URL));
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/BorrowBookControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.BookService;
5 | import de.codecentric.psd.worblehat.domain.Borrowing;
6 | import de.codecentric.psd.worblehat.web.formdata.BorrowBookFormData;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Test;
9 | import org.springframework.mock.web.MockHttpServletRequest;
10 | import org.springframework.ui.ModelMap;
11 | import org.springframework.validation.BindingResult;
12 | import org.springframework.validation.MapBindingResult;
13 | import org.springframework.validation.ObjectError;
14 |
15 | import java.time.LocalDate;
16 | import java.util.Collections;
17 | import java.util.HashMap;
18 | import java.util.Optional;
19 |
20 | import static org.hamcrest.CoreMatchers.is;
21 | import static org.hamcrest.MatcherAssert.assertThat;
22 | import static org.hamcrest.Matchers.not;
23 | import static org.hamcrest.Matchers.nullValue;
24 | import static org.mockito.ArgumentMatchers.any;
25 | import static org.mockito.Mockito.*;
26 |
27 | class BorrowBookControllerTest {
28 |
29 | private BookService bookService;
30 |
31 | private BorrowBookController borrowBookController;
32 |
33 | private BindingResult bindingResult;
34 |
35 | private BorrowBookFormData bookBorrowFormData;
36 |
37 | private static final Book TEST_BOOK = new Book("title", "author", "edition", "isbn", 2016);
38 |
39 | public static final String BORROWER_EMAIL = "someone@codecentric.de";
40 |
41 | @BeforeEach
42 | void setUp() {
43 | bookService = mock(BookService.class);
44 | bindingResult = new MapBindingResult(new HashMap<>(), "");
45 | bookBorrowFormData = new BorrowBookFormData();
46 | borrowBookController = new BorrowBookController(bookService);
47 | }
48 |
49 | @Test
50 | void shouldSetupForm() {
51 | ModelMap modelMap = new ModelMap();
52 |
53 | borrowBookController.setupForm(modelMap);
54 |
55 | assertThat(modelMap.get("borrowFormData"), is(not(nullValue())));
56 | }
57 |
58 | @Test
59 | void shouldNavigateToBorrowWhenResultHasErrors() {
60 | bindingResult.addError(new ObjectError("", ""));
61 |
62 | String navigateTo = borrowBookController.processSubmit(bookBorrowFormData, bindingResult);
63 |
64 | assertThat(navigateTo, is("borrow"));
65 | }
66 |
67 | @Test
68 | void shouldRejectBorrowingIfBookDoesNotExist() {
69 | when(bookService.findBooksByIsbn(TEST_BOOK.getIsbn())).thenReturn(null);
70 |
71 | String navigateTo = borrowBookController.processSubmit(bookBorrowFormData, bindingResult);
72 |
73 | assertThat(bindingResult.hasFieldErrors("isbn"), is(true));
74 | assertThat(navigateTo, is("borrow"));
75 | }
76 |
77 | @Test
78 | void shouldRejectAlreadyBorrowedBooks() {
79 | bookBorrowFormData.setEmail(BORROWER_EMAIL);
80 | bookBorrowFormData.setIsbn(TEST_BOOK.getIsbn());
81 | when(bookService.findBooksByIsbn(TEST_BOOK.getIsbn()))
82 | .thenReturn(Collections.singleton(TEST_BOOK));
83 | String navigateTo = borrowBookController.processSubmit(bookBorrowFormData, bindingResult);
84 |
85 | assertThat(bindingResult.hasFieldErrors("isbn"), is(true));
86 | assertThat(bindingResult.getFieldError("isbn").getCode(), is("noBorrowableBooks"));
87 | assertThat(navigateTo, is("borrow"));
88 | }
89 |
90 | @Test
91 | void shouldNavigateHomeOnSuccess() {
92 | bookBorrowFormData.setEmail(BORROWER_EMAIL);
93 | bookBorrowFormData.setIsbn(TEST_BOOK.getIsbn());
94 | when(bookService.findBooksByIsbn(TEST_BOOK.getIsbn()))
95 | .thenReturn(Collections.singleton(TEST_BOOK));
96 | when(bookService.borrowBook(any(), any()))
97 | .thenReturn(Optional.of(new Borrowing(TEST_BOOK, BORROWER_EMAIL, LocalDate.now())));
98 |
99 | String navigateTo = borrowBookController.processSubmit(bookBorrowFormData, bindingResult);
100 | verify(bookService).borrowBook(TEST_BOOK.getIsbn(), BORROWER_EMAIL);
101 | assertThat(navigateTo, is("home"));
102 | }
103 |
104 | @Test
105 | void shouldNavigateToHomeOnErrors() {
106 | String navigateTo =
107 | borrowBookController.handleErrors(new Exception(), new MockHttpServletRequest());
108 |
109 | assertThat(navigateTo, is("home"));
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/BorrowedBookListControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static de.codecentric.psd.worblehat.web.controller.BorrowingTestData.borrowingWith;
4 | import static de.codecentric.psd.worblehat.web.controller.ReturnAllBooksFormTestData.emailAddress;
5 | import static org.assertj.core.api.Assertions.assertThat;
6 | import static org.mockito.Mockito.when;
7 |
8 | import de.codecentric.psd.worblehat.domain.BookService;
9 | import de.codecentric.psd.worblehat.domain.Borrowing;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import org.junit.jupiter.api.Test;
13 | import org.mockito.InjectMocks;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.jupiter.MockitoSettings;
16 | import org.springframework.ui.ModelMap;
17 | import org.springframework.validation.MapBindingResult;
18 |
19 | @MockitoSettings
20 | class BorrowedBookListControllerTest {
21 |
22 | @Mock private BookService bookServiceMock;
23 |
24 | @InjectMocks private BorrowedBookListController controllerUnderTest;
25 |
26 | @Test
27 | void setupForm() {
28 | var modelMap = new ModelMap();
29 |
30 | controllerUnderTest.get(modelMap);
31 |
32 | assertThat(modelMap.get("returnAllBookFormData")).isNotNull();
33 | }
34 |
35 | @Test
36 | void shouldReturnOneBorrowedBook() {
37 | String emailAddress = "sandra@worblehat.net";
38 | when(bookServiceMock.findAllBorrowingsByEmailAddress(emailAddress))
39 | .thenReturn(List.of(borrowingWith(emailAddress)));
40 |
41 | var borrowedBookList = new ModelMap();
42 | String page =
43 | controllerUnderTest.findBorrowedBooksByEmailAddress(
44 | emailAddress(emailAddress),
45 | new MapBindingResult(new HashMap<>(), ""),
46 | borrowedBookList);
47 |
48 | assertThat(page).isEqualTo("borrowedBookList");
49 | assertThat(borrowedBookList.get("borrowings")).isInstanceOf(List.class);
50 | List borrowedBooks = (List) borrowedBookList.get("borrowings");
51 | assertThat(borrowedBooks).hasSize(1);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/BorrowingTestData.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.Book;
4 | import de.codecentric.psd.worblehat.domain.Borrowing;
5 |
6 | import java.time.LocalDate;
7 |
8 | public class BorrowingTestData {
9 |
10 | private BorrowingTestData() {}
11 |
12 | static Borrowing borrowingWith(String emailAddress) {
13 | Book aBook = new Book("Title", "Author", "1", "isbn", 2020);
14 | return new Borrowing(aBook, emailAddress, LocalDate.now());
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/InsertBookControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 | import static org.hamcrest.Matchers.not;
6 | import static org.hamcrest.Matchers.nullValue;
7 | import static org.mockito.ArgumentMatchers.any;
8 | import static org.mockito.Mockito.*;
9 |
10 | import de.codecentric.psd.worblehat.domain.Book;
11 | import de.codecentric.psd.worblehat.domain.BookParameter;
12 | import de.codecentric.psd.worblehat.domain.BookService;
13 | import de.codecentric.psd.worblehat.web.formdata.InsertBookFormData;
14 | import java.util.HashMap;
15 | import java.util.Optional;
16 | import org.junit.jupiter.api.BeforeEach;
17 | import org.junit.jupiter.api.Test;
18 | import org.mockito.ArgumentCaptor;
19 | import org.springframework.ui.ModelMap;
20 | import org.springframework.validation.BindingResult;
21 | import org.springframework.validation.MapBindingResult;
22 | import org.springframework.validation.ObjectError;
23 |
24 | class InsertBookControllerTest {
25 |
26 | private InsertBookController insertBookController;
27 |
28 | private BookService bookService;
29 |
30 | private InsertBookFormData insertBookFormData;
31 |
32 | private BindingResult bindingResult;
33 |
34 | private static final Book TEST_BOOK = new Book("title", "author", "edition", "isbn", 2016);
35 |
36 | @BeforeEach
37 | void setUp() {
38 | bookService = mock(BookService.class);
39 | insertBookController = new InsertBookController(bookService);
40 | insertBookFormData = new InsertBookFormData();
41 | bindingResult = new MapBindingResult(new HashMap<>(), "");
42 | TEST_BOOK.setDescription("Description");
43 | }
44 |
45 | @Test
46 | void shouldSetupForm() {
47 | ModelMap modelMap = new ModelMap();
48 |
49 | insertBookController.setupForm(modelMap);
50 |
51 | assertThat(modelMap.get("insertBookFormData"), is(not(nullValue())));
52 | }
53 |
54 | @Test
55 | void shouldRejectErrors() {
56 | bindingResult.addError(new ObjectError("", ""));
57 |
58 | String navigateTo = insertBookController.processSubmit(insertBookFormData, bindingResult);
59 |
60 | assertThat(navigateTo, is("insertBooks"));
61 | }
62 |
63 | @Test
64 | void shouldCreateNewCopyOfExistingBook() {
65 | setupFormData();
66 | when(bookService.bookExists(TEST_BOOK.getIsbn())).thenReturn(true);
67 | when(bookService.createBook(any())).thenReturn(Optional.of(TEST_BOOK));
68 |
69 | String navigateTo = insertBookController.processSubmit(insertBookFormData, bindingResult);
70 |
71 | verifyBookIsCreated();
72 | assertThat(navigateTo, is("redirect:bookList"));
73 | }
74 |
75 | @Test
76 | void shouldCreateBookAndNavigateToBookList() {
77 | setupFormData();
78 | when(bookService.bookExists(TEST_BOOK.getIsbn())).thenReturn(false);
79 | when(bookService.createBook(any())).thenReturn(Optional.of(TEST_BOOK));
80 |
81 | String navigateTo = insertBookController.processSubmit(insertBookFormData, bindingResult);
82 |
83 | verifyBookIsCreated();
84 | assertThat(navigateTo, is("redirect:bookList"));
85 | }
86 |
87 | private void verifyBookIsCreated() {
88 | ArgumentCaptor bookArgumentCaptor = ArgumentCaptor.forClass(BookParameter.class);
89 | verify(bookService).createBook(bookArgumentCaptor.capture());
90 | BookParameter bookParameter = bookArgumentCaptor.getValue();
91 | assertThat(bookParameter.getTitle(), is(TEST_BOOK.getTitle()));
92 | assertThat(bookParameter.getAuthor(), is(TEST_BOOK.getAuthor()));
93 | assertThat(bookParameter.getEdition(), is(TEST_BOOK.getEdition()));
94 | assertThat(bookParameter.getIsbn(), is(TEST_BOOK.getIsbn()));
95 | assertThat(bookParameter.getYearOfPublication(), is(TEST_BOOK.getYearOfPublication()));
96 | assertThat(bookParameter.getDescription(), is(TEST_BOOK.getDescription()));
97 | }
98 |
99 | private void setupFormData() {
100 | insertBookFormData.setTitle(TEST_BOOK.getTitle());
101 | insertBookFormData.setAuthor(TEST_BOOK.getAuthor());
102 | insertBookFormData.setEdition(TEST_BOOK.getEdition());
103 | insertBookFormData.setIsbn(TEST_BOOK.getIsbn());
104 | insertBookFormData.setYearOfPublication(String.valueOf(TEST_BOOK.getYearOfPublication()));
105 | insertBookFormData.setDescription(TEST_BOOK.getDescription());
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/NavigationControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 |
6 | import org.junit.jupiter.api.Test;
7 |
8 | class NavigationControllerTest {
9 |
10 | @Test
11 | void shouldNavigateToHome() throws Exception {
12 | String navigateTo = new NavigationController().home();
13 |
14 | assertThat(navigateTo, is("home"));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/RemoveBookControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.domain.BookService;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Test;
6 | import org.mockito.Mock;
7 |
8 | import static org.hamcrest.CoreMatchers.*;
9 | import static org.hamcrest.MatcherAssert.assertThat;
10 | import static org.mockito.Mockito.*;
11 |
12 | public class RemoveBookControllerTest {
13 |
14 | private RemoveBookController removeBookController;
15 |
16 | //@Mock
17 | BookService bookService;
18 |
19 | @BeforeEach
20 | public void setup() {
21 | bookService = mock(BookService.class);
22 | removeBookController = new RemoveBookController(bookService);
23 | }
24 |
25 | @Test
26 | public void shouldRemoveBook() {
27 | String result = removeBookController.removeBook("anIsbn");
28 | verify(bookService).removeBook("anIsbn");
29 | assertThat(result, is("redirect:bookList"));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/ReturnAllBooksControllerTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import static org.hamcrest.CoreMatchers.is;
4 | import static org.hamcrest.MatcherAssert.assertThat;
5 | import static org.hamcrest.Matchers.not;
6 | import static org.hamcrest.Matchers.nullValue;
7 | import static org.mockito.Mockito.mock;
8 | import static org.mockito.Mockito.verify;
9 |
10 | import de.codecentric.psd.worblehat.domain.BookService;
11 | import de.codecentric.psd.worblehat.web.formdata.ReturnAllBooksFormData;
12 | import java.util.HashMap;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 | import org.springframework.ui.ModelMap;
16 | import org.springframework.validation.BindingResult;
17 | import org.springframework.validation.MapBindingResult;
18 | import org.springframework.validation.ObjectError;
19 |
20 | class ReturnAllBooksControllerTest {
21 |
22 | private ReturnAllBooksController returnAllBooksController;
23 |
24 | private BookService bookService;
25 |
26 | private ReturnAllBooksFormData returnAllBooksFormData;
27 |
28 | private BindingResult bindingResult;
29 |
30 | @BeforeEach
31 | void setUp() throws Exception {
32 | bookService = mock(BookService.class);
33 | returnAllBooksController = new ReturnAllBooksController(bookService);
34 | returnAllBooksFormData = new ReturnAllBooksFormData();
35 | bindingResult = new MapBindingResult(new HashMap<>(), "");
36 | }
37 |
38 | @Test
39 | void shouldSetupForm() throws Exception {
40 | ModelMap modelMap = new ModelMap();
41 |
42 | returnAllBooksController.prepareView(modelMap);
43 |
44 | assertThat(modelMap.get("returnAllBookFormData"), is(not(nullValue())));
45 | }
46 |
47 | @Test
48 | void shouldRejectErrors() throws Exception {
49 | bindingResult.addError(new ObjectError("", ""));
50 |
51 | String navigateTo =
52 | returnAllBooksController.returnAllBooks(returnAllBooksFormData, bindingResult);
53 |
54 | assertThat(navigateTo, is("returnAllBooks"));
55 | }
56 |
57 | @Test
58 | void shouldReturnAllBooksAndNavigateHome() throws Exception {
59 | String borrower = "someone@codecentric.de";
60 | returnAllBooksFormData.setEmailAddress(borrower);
61 |
62 | String navigateTo =
63 | returnAllBooksController.returnAllBooks(returnAllBooksFormData, bindingResult);
64 |
65 | verify(bookService).returnAllBooksByBorrower(borrower);
66 | assertThat(navigateTo, is("home"));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/controller/ReturnAllBooksFormTestData.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.controller;
2 |
3 | import de.codecentric.psd.worblehat.web.formdata.ReturnAllBooksFormData;
4 |
5 | public class ReturnAllBooksFormTestData {
6 |
7 | private ReturnAllBooksFormTestData() {}
8 |
9 | static ReturnAllBooksFormData anEmailAddress() {
10 | var returnAllBooksFormData = new ReturnAllBooksFormData();
11 | returnAllBooksFormData.setEmailAddress("sandra@worblehat.net");
12 | return returnAllBooksFormData;
13 | }
14 |
15 | static ReturnAllBooksFormData emailAddress(String emailAddress) {
16 | var returnAllBooksFormData = new ReturnAllBooksFormData();
17 | returnAllBooksFormData.setEmailAddress(emailAddress);
18 | return returnAllBooksFormData;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/validation/ISBNConstraintValidatorTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertFalse;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 | import static org.mockito.Mockito.mock;
6 |
7 | import javax.validation.ConstraintValidatorContext;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 |
11 | class ISBNConstraintValidatorTest {
12 |
13 | private ISBNConstraintValidator isbnConstraintValidator;
14 |
15 | private ConstraintValidatorContext constraintValidatorContext;
16 |
17 | @BeforeEach
18 | void setUp() throws Exception {
19 | isbnConstraintValidator = new ISBNConstraintValidator();
20 | constraintValidatorContext = mock(ConstraintValidatorContext.class);
21 | }
22 |
23 | @Test
24 | void initializeShouldTakeIsbn() throws Exception {
25 | ISBN isbn = mock(ISBN.class);
26 | isbnConstraintValidator.initialize(isbn);
27 | }
28 |
29 | @Test
30 | void shouldReturnTrueIfBlank() throws Exception {
31 | boolean actual = isbnConstraintValidator.isValid("", constraintValidatorContext);
32 | assertTrue(actual);
33 | }
34 |
35 | @Test
36 | void shouldReturnTrueIfValidISBN10() throws Exception {
37 | boolean actual = isbnConstraintValidator.isValid("0132350882", constraintValidatorContext);
38 | assertTrue(actual);
39 | }
40 |
41 | @Test
42 | void shouldReturnFalseIfInvalidISBN10() throws Exception {
43 | boolean actual = isbnConstraintValidator.isValid("0123459789", constraintValidatorContext);
44 | assertFalse(actual);
45 | }
46 |
47 | @Test
48 | void shouldReturnTrueForValidISBN13() throws Exception {
49 | boolean actual = isbnConstraintValidator.isValid("9783827317247", constraintValidatorContext);
50 | assertTrue(actual);
51 | }
52 |
53 | @Test
54 | void shouldReturnFalseForInvalidISBN13() {
55 | boolean actual = isbnConstraintValidator.isValid("1234567890XXX", constraintValidatorContext);
56 | assertFalse(actual);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/java/de/codecentric/psd/worblehat/web/validation/NumericConstraintValidatorTest.java:
--------------------------------------------------------------------------------
1 | package de.codecentric.psd.worblehat.web.validation;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertFalse;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 | import static org.mockito.Mockito.mock;
6 |
7 | import javax.validation.ConstraintValidatorContext;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 |
11 | class NumericConstraintValidatorTest {
12 |
13 | private NumericConstraintValidator numericConstraintValidator;
14 |
15 | ConstraintValidatorContext constraintValidatorContext;
16 |
17 | @BeforeEach
18 | void setUp() throws Exception {
19 | numericConstraintValidator = new NumericConstraintValidator();
20 | constraintValidatorContext = mock(ConstraintValidatorContext.class);
21 | }
22 |
23 | @Test
24 | void initializeShouldTakeNumeric() throws Exception {
25 | Numeric numeric = mock(Numeric.class);
26 | numericConstraintValidator.initialize(numeric);
27 | }
28 |
29 | @Test
30 | void shouldReturnTrueIfBlank() throws Exception {
31 | boolean actual = numericConstraintValidator.isValid("", constraintValidatorContext);
32 | assertTrue(actual);
33 | }
34 |
35 | @Test
36 | void shouldReturnTrueIfNumeric() throws Exception {
37 | boolean actual = numericConstraintValidator.isValid("1", constraintValidatorContext);
38 | assertTrue(actual);
39 | }
40 |
41 | @Test
42 | void shouldReturnFalsIfNotNumeric() throws Exception {
43 | boolean actual = numericConstraintValidator.isValid("x", constraintValidatorContext);
44 | assertFalse(actual);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:tc:postgresql:9.6.8://foo/bar
2 | spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
3 | spring.liquibase.change-log=classpath:master.xml
4 | spring.liquibase.drop-first=true
5 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
6 |
--------------------------------------------------------------------------------
/worblehat-web/src/test/resources/log4j.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------